cpp_thunk.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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/Lookup.h"
  8. #include "clang/Sema/Sema.h"
  9. #include "toolchain/check/call.h"
  10. #include "toolchain/check/context.h"
  11. #include "toolchain/check/control_flow.h"
  12. #include "toolchain/check/convert.h"
  13. #include "toolchain/check/literal.h"
  14. #include "toolchain/check/type.h"
  15. #include "toolchain/check/type_completion.h"
  16. #include "toolchain/sem_ir/ids.h"
  17. #include "toolchain/sem_ir/typed_insts.h"
  18. namespace Carbon::Check {
  19. // Returns the C++ thunk mangled name given the callee function.
  20. static auto GenerateThunkMangledName(
  21. clang::MangleContext& mangle_context,
  22. const clang::FunctionDecl& callee_function_decl) -> std::string {
  23. RawStringOstream mangled_name_stream;
  24. mangle_context.mangleName(clang::GlobalDecl(&callee_function_decl),
  25. mangled_name_stream);
  26. mangled_name_stream << ".carbon_thunk";
  27. return mangled_name_stream.TakeStr();
  28. }
  29. // Returns true if a C++ thunk is required for the given type. A C++ thunk is
  30. // required for any type except for void, pointer types and signed 32-bit and
  31. // 64-bit integers.
  32. static auto IsThunkRequiredForType(Context& context, SemIR::TypeId type_id)
  33. -> bool {
  34. if (!type_id.has_value() || type_id == SemIR::ErrorInst::TypeId) {
  35. return false;
  36. }
  37. type_id = context.types().GetUnqualifiedType(type_id);
  38. switch (context.types().GetAsInst(type_id).kind()) {
  39. case SemIR::PointerType::Kind: {
  40. return false;
  41. }
  42. case SemIR::ClassType::Kind: {
  43. if (!context.types().IsComplete(type_id)) {
  44. // Signed integers of 32 or 64 bits should be completed when imported.
  45. return true;
  46. }
  47. auto int_info = context.types().TryGetIntTypeInfo(type_id);
  48. if (!int_info || !int_info->bit_width.has_value()) {
  49. return true;
  50. }
  51. llvm::APInt bit_width = context.ints().Get(int_info->bit_width);
  52. return bit_width != 32 && bit_width != 64;
  53. }
  54. default:
  55. return true;
  56. }
  57. }
  58. auto IsCppThunkRequired(Context& context, const SemIR::Function& function)
  59. -> bool {
  60. if (!function.clang_decl_id.has_value()) {
  61. return false;
  62. }
  63. SemIR::TypeId return_type_id =
  64. function.GetDeclaredReturnType(context.sem_ir());
  65. if (return_type_id.has_value()) {
  66. // TODO: Support non-void return values.
  67. return false;
  68. }
  69. bool thunk_required_for_param = false;
  70. for (auto param_id :
  71. context.inst_blocks().GetOrEmpty(function.call_params_id)) {
  72. if (param_id == SemIR::ErrorInst::InstId) {
  73. return false;
  74. }
  75. if (!thunk_required_for_param &&
  76. IsThunkRequiredForType(
  77. context,
  78. context.insts().GetAs<SemIR::AnyParam>(param_id).type_id)) {
  79. thunk_required_for_param = true;
  80. }
  81. }
  82. return thunk_required_for_param;
  83. }
  84. // Returns whether the type is a pointer or a signed int of 32 or 64 bits.
  85. static auto IsSimpleAbiType(clang::ASTContext& ast_context,
  86. clang::QualType type) -> bool {
  87. if (type->isPointerType()) {
  88. return true;
  89. }
  90. if (const auto* builtin_type = type->getAs<clang::BuiltinType>()) {
  91. if (builtin_type->isSignedInteger()) {
  92. uint64_t type_size = ast_context.getIntWidth(type);
  93. return type_size == 32 || type_size == 64;
  94. }
  95. }
  96. return false;
  97. }
  98. namespace {
  99. // Information about the callee of a thunk.
  100. struct CalleeFunctionInfo {
  101. explicit CalleeFunctionInfo(clang::FunctionDecl* decl) : decl(decl) {
  102. const auto* method_decl = dyn_cast<clang::CXXMethodDecl>(decl);
  103. has_object_parameter = method_decl && !method_decl->isStatic() &&
  104. !isa<clang::CXXConstructorDecl>(method_decl);
  105. if (has_object_parameter && method_decl->isImplicitObjectMemberFunction()) {
  106. implicit_this_type = method_decl->getThisType();
  107. }
  108. }
  109. // Returns whether this callee has an implicit `this` parameter.
  110. auto has_implicit_object_parameter() const -> bool {
  111. return !implicit_this_type.isNull();
  112. }
  113. // Returns whether this callee has an explicit `this` parameter.
  114. auto has_explicit_object_parameter() const -> bool {
  115. return has_object_parameter && !has_implicit_object_parameter();
  116. }
  117. // Returns the number of parameters the thunk should have.
  118. auto num_thunk_params() const -> unsigned {
  119. return has_implicit_object_parameter() + decl->getNumParams();
  120. }
  121. auto GetThunkParamIndex(unsigned callee_param_index) const -> unsigned {
  122. return has_implicit_object_parameter() + callee_param_index;
  123. }
  124. // The callee function.
  125. clang::FunctionDecl* decl;
  126. // Whether the callee has an object parameter, which might be explicit or
  127. // implicit.
  128. bool has_object_parameter;
  129. // If the callee has an implicit object parameter, the corresponding `this`
  130. // type. Otherwise a null type.
  131. clang::QualType implicit_this_type;
  132. };
  133. } // namespace
  134. // Creates the thunk parameter types given the callee function.
  135. static auto BuildThunkParameterTypes(clang::ASTContext& ast_context,
  136. CalleeFunctionInfo callee_info)
  137. -> llvm::SmallVector<clang::QualType> {
  138. llvm::SmallVector<clang::QualType> thunk_param_types;
  139. thunk_param_types.reserve(callee_info.num_thunk_params());
  140. if (callee_info.has_implicit_object_parameter()) {
  141. thunk_param_types.push_back(ast_context.getAttributedType(
  142. clang::NullabilityKind::NonNull, callee_info.implicit_this_type,
  143. callee_info.implicit_this_type));
  144. }
  145. for (const clang::ParmVarDecl* callee_param :
  146. callee_info.decl->parameters()) {
  147. clang::QualType param_type = callee_param->getType();
  148. bool is_simple_abi_type = IsSimpleAbiType(ast_context, param_type);
  149. if (!is_simple_abi_type) {
  150. clang::QualType pointer_type = ast_context.getPointerType(param_type);
  151. param_type = ast_context.getAttributedType(
  152. clang::NullabilityKind::NonNull, pointer_type, pointer_type);
  153. }
  154. thunk_param_types.push_back(param_type);
  155. }
  156. CARBON_CHECK(thunk_param_types.size() == callee_info.num_thunk_params());
  157. return thunk_param_types;
  158. }
  159. // Returns the thunk parameters using the callee function parameter identifiers.
  160. static auto BuildThunkParameters(clang::ASTContext& ast_context,
  161. CalleeFunctionInfo callee_info,
  162. clang::FunctionDecl* thunk_function_decl)
  163. -> llvm::SmallVector<clang::ParmVarDecl*> {
  164. clang::SourceLocation clang_loc = callee_info.decl->getLocation();
  165. const auto* thunk_function_proto_type =
  166. thunk_function_decl->getFunctionType()->getAs<clang::FunctionProtoType>();
  167. llvm::SmallVector<clang::ParmVarDecl*> thunk_params;
  168. unsigned num_thunk_params = thunk_function_decl->getNumParams();
  169. thunk_params.reserve(num_thunk_params);
  170. if (callee_info.has_implicit_object_parameter()) {
  171. clang::ParmVarDecl* thunk_param =
  172. clang::ParmVarDecl::Create(ast_context, thunk_function_decl, clang_loc,
  173. clang_loc, &ast_context.Idents.get("this"),
  174. thunk_function_proto_type->getParamType(0),
  175. nullptr, clang::SC_None, nullptr);
  176. thunk_params.push_back(thunk_param);
  177. }
  178. for (unsigned i : llvm::seq(callee_info.decl->getNumParams())) {
  179. clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create(
  180. ast_context, thunk_function_decl, clang_loc, clang_loc,
  181. callee_info.decl->getParamDecl(i)->getIdentifier(),
  182. thunk_function_proto_type->getParamType(
  183. callee_info.GetThunkParamIndex(i)),
  184. nullptr, clang::SC_None, nullptr);
  185. thunk_params.push_back(thunk_param);
  186. }
  187. CARBON_CHECK(thunk_params.size() == num_thunk_params);
  188. return thunk_params;
  189. }
  190. // Returns the thunk function declaration given the callee function and the
  191. // thunk parameter types.
  192. static auto CreateThunkFunctionDecl(
  193. Context& context, CalleeFunctionInfo callee_info,
  194. llvm::ArrayRef<clang::QualType> thunk_param_types) -> clang::FunctionDecl* {
  195. clang::ASTContext& ast_context = context.ast_context();
  196. clang::SourceLocation clang_loc = callee_info.decl->getLocation();
  197. clang::IdentifierInfo& identifier_info = ast_context.Idents.get(
  198. callee_info.decl->getNameAsString() + "__carbon_thunk");
  199. auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
  200. clang::QualType thunk_function_type = ast_context.getFunctionType(
  201. callee_info.decl->getReturnType(), thunk_param_types, ext_proto_info);
  202. clang::DeclContext* decl_context = ast_context.getTranslationUnitDecl();
  203. // TODO: Thunks should not have external linkage, consider using `SC_Static`.
  204. clang::FunctionDecl* thunk_function_decl = clang::FunctionDecl::Create(
  205. ast_context, decl_context, clang_loc, clang_loc,
  206. clang::DeclarationName(&identifier_info), thunk_function_type,
  207. /*TInfo=*/nullptr, clang::SC_Extern);
  208. decl_context->addDecl(thunk_function_decl);
  209. thunk_function_decl->setParams(
  210. BuildThunkParameters(ast_context, callee_info, thunk_function_decl));
  211. // Set always_inline.
  212. thunk_function_decl->addAttr(
  213. clang::AlwaysInlineAttr::CreateImplicit(ast_context));
  214. // Set asm("<callee function mangled name>.carbon_thunk").
  215. thunk_function_decl->addAttr(clang::AsmLabelAttr::CreateImplicit(
  216. ast_context,
  217. GenerateThunkMangledName(*context.sem_ir().clang_mangle_context(),
  218. *callee_info.decl),
  219. clang_loc));
  220. return thunk_function_decl;
  221. }
  222. // Builds a reference to the given parameter thunk, which is carrying a value of
  223. // the given type.
  224. static auto BuildThunkParamRef(clang::Sema& sema,
  225. clang::FunctionDecl* thunk_function_decl,
  226. unsigned thunk_index, clang::QualType type)
  227. -> clang::Expr* {
  228. clang::ParmVarDecl* thunk_param =
  229. thunk_function_decl->getParamDecl(thunk_index);
  230. clang::SourceLocation clang_loc = thunk_param->getLocation();
  231. clang::Expr* call_arg = sema.BuildDeclRefExpr(
  232. thunk_param, thunk_param->getType().getNonReferenceType(),
  233. clang::VK_LValue, clang_loc);
  234. if (thunk_param->getType() != type) {
  235. // TODO: Consider inserting a cast to an rvalue. Note that we currently
  236. // pass pointers to non-temporary objects as the argument when calling a
  237. // thunk, so we'll need to either change that or generate different thunks
  238. // depending on whether we're moving from each parameter.
  239. clang::ExprResult deref_result =
  240. sema.BuildUnaryOp(nullptr, clang_loc, clang::UO_Deref, call_arg);
  241. CARBON_CHECK(deref_result.isUsable());
  242. call_arg = deref_result.get();
  243. }
  244. return call_arg;
  245. }
  246. // Builds a reference to the parameter thunk parameter corresponding to the
  247. // given callee parameter index.
  248. static auto BuildParamRefForCalleeArg(clang::Sema& sema,
  249. clang::FunctionDecl* thunk_function_decl,
  250. CalleeFunctionInfo callee_info,
  251. unsigned callee_index) -> clang::Expr* {
  252. unsigned thunk_index = callee_info.GetThunkParamIndex(callee_index);
  253. return BuildThunkParamRef(
  254. sema, thunk_function_decl, thunk_index,
  255. callee_info.decl->getParamDecl(callee_index)->getType());
  256. }
  257. // Builds an argument list for the callee function by creating suitable uses of
  258. // the corresponding thunk parameters.
  259. static auto BuildCalleeArgs(clang::Sema& sema,
  260. clang::FunctionDecl* thunk_function_decl,
  261. CalleeFunctionInfo callee_info)
  262. -> llvm::SmallVector<clang::Expr*> {
  263. llvm::SmallVector<clang::Expr*> call_args;
  264. // The object parameter is always passed as `self`, not in the callee argument
  265. // list, so the first argument corresponds to the second parameter if there is
  266. // an explicit object parameter and the first parameter otherwise.
  267. unsigned first_param = callee_info.has_explicit_object_parameter();
  268. unsigned num_params = callee_info.decl->getNumParams();
  269. call_args.reserve(num_params - first_param);
  270. for (unsigned callee_index : llvm::seq(first_param, num_params)) {
  271. call_args.push_back(BuildParamRefForCalleeArg(sema, thunk_function_decl,
  272. callee_info, callee_index));
  273. }
  274. return call_args;
  275. }
  276. // Builds the thunk function body which calls the callee function using the call
  277. // args and returns the callee function return value. Returns nullptr on
  278. // failure.
  279. static auto BuildThunkBody(clang::Sema& sema,
  280. clang::FunctionDecl* thunk_function_decl,
  281. CalleeFunctionInfo callee_info)
  282. -> clang::StmtResult {
  283. clang::SourceLocation clang_loc = callee_info.decl->getLocation();
  284. // If the callee has an object parameter, build a member access expression as
  285. // the callee. Otherwise, build a regular reference to the function.
  286. clang::ExprResult callee;
  287. if (callee_info.has_object_parameter) {
  288. auto* object_param_ref =
  289. BuildThunkParamRef(sema, thunk_function_decl, 0,
  290. callee_info.has_implicit_object_parameter()
  291. ? thunk_function_decl->getParamDecl(0)->getType()
  292. : callee_info.decl->getParamDecl(0)->getType());
  293. bool is_arrow = callee_info.has_implicit_object_parameter();
  294. auto object =
  295. sema.PerformMemberExprBaseConversion(object_param_ref, is_arrow);
  296. if (object.isInvalid()) {
  297. return clang::StmtError();
  298. }
  299. callee = sema.BuildMemberExpr(
  300. object.get(), is_arrow, clang_loc, clang::NestedNameSpecifierLoc(),
  301. clang::SourceLocation(), callee_info.decl,
  302. clang::DeclAccessPair::make(callee_info.decl, clang::AS_public),
  303. /*HadMultipleCandidates=*/false, clang::DeclarationNameInfo(),
  304. sema.getASTContext().BoundMemberTy, clang::VK_PRValue,
  305. clang::OK_Ordinary);
  306. } else {
  307. callee =
  308. sema.BuildDeclRefExpr(callee_info.decl, callee_info.decl->getType(),
  309. clang::VK_PRValue, clang_loc);
  310. }
  311. if (callee.isInvalid()) {
  312. return clang::StmtError();
  313. }
  314. // Build the argument list.
  315. llvm::SmallVector<clang::Expr*> call_args =
  316. BuildCalleeArgs(sema, thunk_function_decl, callee_info);
  317. clang::ExprResult call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc,
  318. call_args, clang_loc);
  319. if (!call.isUsable()) {
  320. return clang::StmtError();
  321. }
  322. // TODO: Consider building a CompoundStmt holding this to make our result more
  323. // closely resemble a real C++ function.
  324. return sema.BuildReturnStmt(clang_loc, call.get());
  325. }
  326. auto BuildCppThunk(Context& context, const SemIR::Function& callee_function)
  327. -> clang::FunctionDecl* {
  328. clang::FunctionDecl* callee_function_decl =
  329. context.sem_ir()
  330. .clang_decls()
  331. .Get(callee_function.clang_decl_id)
  332. .decl->getAsFunction();
  333. CARBON_CHECK(callee_function_decl);
  334. CalleeFunctionInfo callee_info(callee_function_decl);
  335. // Build the thunk function declaration.
  336. auto thunk_param_types =
  337. BuildThunkParameterTypes(context.ast_context(), callee_info);
  338. clang::FunctionDecl* thunk_function_decl =
  339. CreateThunkFunctionDecl(context, callee_info, thunk_param_types);
  340. // Build the thunk function body.
  341. clang::Sema& sema = context.sem_ir().clang_ast_unit()->getSema();
  342. clang::Sema::ContextRAII context_raii(sema, thunk_function_decl);
  343. sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl);
  344. clang::StmtResult body =
  345. BuildThunkBody(sema, thunk_function_decl, callee_info);
  346. sema.ActOnFinishFunctionBody(thunk_function_decl, body.get());
  347. if (body.isInvalid()) {
  348. return nullptr;
  349. }
  350. return thunk_function_decl;
  351. }
  352. auto PerformCppThunkCall(Context& context, SemIR::LocId loc_id,
  353. SemIR::FunctionId callee_function_id,
  354. llvm::ArrayRef<SemIR::InstId> callee_arg_ids,
  355. SemIR::InstId thunk_callee_id) -> SemIR::InstId {
  356. llvm::ArrayRef<SemIR::InstId> callee_function_params =
  357. context.inst_blocks().GetOrEmpty(
  358. context.functions().Get(callee_function_id).call_params_id);
  359. llvm::ArrayRef<SemIR::InstId> thunk_function_params =
  360. context.inst_blocks().GetOrEmpty(
  361. context.functions()
  362. .Get(GetCalleeFunction(context.sem_ir(), thunk_callee_id)
  363. .function_id)
  364. .call_params_id);
  365. size_t num_params = callee_function_params.size();
  366. CARBON_CHECK(thunk_function_params.size() == num_params);
  367. CARBON_CHECK(callee_arg_ids.size() == num_params);
  368. llvm::SmallVector<SemIR::InstId> thunk_arg_ids;
  369. thunk_arg_ids.reserve(num_params);
  370. for (auto [callee_param_inst_id, thunk_param_inst_id, callee_arg_id] :
  371. llvm::zip(callee_function_params, thunk_function_params,
  372. callee_arg_ids)) {
  373. SemIR::TypeId callee_param_type_id =
  374. context.insts().GetAs<SemIR::AnyParam>(callee_param_inst_id).type_id;
  375. SemIR::TypeId thunk_param_type_id =
  376. context.insts().GetAs<SemIR::AnyParam>(thunk_param_inst_id).type_id;
  377. SemIR::InstId arg_id = callee_arg_id;
  378. if (callee_param_type_id != thunk_param_type_id) {
  379. arg_id = Convert(context, loc_id, arg_id,
  380. {.kind = ConversionTarget::CppThunkRef,
  381. .type_id = callee_param_type_id});
  382. arg_id = AddInst<SemIR::AddrOf>(
  383. context, loc_id,
  384. {.type_id = GetPointerType(
  385. context, context.types().GetInstId(callee_param_type_id)),
  386. .lvalue_id = arg_id});
  387. arg_id =
  388. ConvertToValueOfType(context, loc_id, arg_id, thunk_param_type_id);
  389. }
  390. thunk_arg_ids.push_back(arg_id);
  391. }
  392. return PerformCall(context, loc_id, thunk_callee_id, thunk_arg_ids);
  393. }
  394. } // namespace Carbon::Check