mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Move C++ interop related check code files to a cpp dir (#6065)
Context: https://github.com/carbon-language/carbon-lang/pull/5891#pullrequestreview-3178216893
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
// 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/custom_type_mapping.h"
|
||||
|
||||
#include "clang/AST/DeclCXX.h"
|
||||
#include "clang/AST/DeclTemplate.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// A small, lightweight library of AST matchers. Unlike clang's ASTMatchers,
|
||||
// this avoids heap allocations and is suitable for one-off matching rather than
|
||||
// matching against a whole AST.
|
||||
namespace Matchers {
|
||||
// A matcher for a type T is just a function that takes a T and returns whether
|
||||
// it matched. Matchers should be invoked immediately, and are not expected to
|
||||
// outlive the arguments of the call that created them.
|
||||
// TODO: We could avoid the indirect calls by making the below functions be
|
||||
// templated on the inner matcher.
|
||||
template <typename T>
|
||||
using Matcher = llvm::function_ref<auto(T)->bool>;
|
||||
|
||||
// Returns a matcher for class declarations that determines whether the given
|
||||
// class is a class template specialization in namespace std with the specified
|
||||
// name and template arguments matching the given predicate.
|
||||
static auto StdClassTemplate(
|
||||
llvm::StringLiteral name,
|
||||
Matcher<const clang::TemplateArgumentList&> args_matcher
|
||||
[[clang::lifetimebound]]) -> auto {
|
||||
return [=](const clang::CXXRecordDecl* class_decl) -> bool {
|
||||
const auto* specialization =
|
||||
dyn_cast<clang::ClassTemplateSpecializationDecl>(class_decl);
|
||||
const auto* identifier = class_decl->getIdentifier();
|
||||
return specialization && identifier && identifier->isStr(name) &&
|
||||
specialization->isInStdNamespace() &&
|
||||
args_matcher(specialization->getTemplateArgs());
|
||||
};
|
||||
}
|
||||
|
||||
// Returns a matcher that matches types if they are class types whose class
|
||||
// matches the given matcher.
|
||||
static auto Class(Matcher<const clang::CXXRecordDecl*> class_matcher
|
||||
[[clang::lifetimebound]]) -> auto {
|
||||
return [=](clang::QualType type) -> bool {
|
||||
const auto* class_decl = type->getAsCXXRecordDecl();
|
||||
return !type.hasQualifiers() && class_decl && class_matcher(class_decl);
|
||||
};
|
||||
}
|
||||
|
||||
// Returns a matcher that determines whether the given template argument is a
|
||||
// type matching the given predicate.
|
||||
static auto TypeTemplateArgument(Matcher<clang::QualType> type_matcher
|
||||
[[clang::lifetimebound]]) -> auto {
|
||||
return [=](clang::TemplateArgument arg) -> bool {
|
||||
return arg.getKind() == clang::TemplateArgument::Type &&
|
||||
type_matcher(arg.getAsType());
|
||||
};
|
||||
}
|
||||
|
||||
// A matcher that determines whether the given type is `char`.
|
||||
static auto Char(clang::QualType type) -> bool {
|
||||
return !type.hasQualifiers() && type->isCharType();
|
||||
}
|
||||
|
||||
// Returns a matcher that determines whether the given template argument list
|
||||
// matches the given sequence of template argument matchers.
|
||||
static auto TemplateArgumentsAre(
|
||||
std::initializer_list<Matcher<clang::TemplateArgument>> arg_matchers
|
||||
[[clang::lifetimebound]]) -> auto {
|
||||
return [=](const clang::TemplateArgumentList& args) -> bool {
|
||||
if (args.size() != arg_matchers.size()) {
|
||||
return false;
|
||||
}
|
||||
for (auto [arg, matcher] : llvm::zip_equal(args.asArray(), arg_matchers)) {
|
||||
if (!matcher(arg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
// A matcher for `std::char_traits<char>`.
|
||||
static auto StdCharTraitsChar(clang::QualType type) -> bool {
|
||||
return Class(StdClassTemplate(
|
||||
"char_traits", TemplateArgumentsAre({TypeTemplateArgument(Char)})))(type);
|
||||
}
|
||||
|
||||
// A matcher for `std::string_view`.
|
||||
static auto StdStringView(const clang::CXXRecordDecl* record_decl) -> bool {
|
||||
return StdClassTemplate(
|
||||
"basic_string_view",
|
||||
TemplateArgumentsAre({TypeTemplateArgument(Char),
|
||||
TypeTemplateArgument(StdCharTraitsChar)}))(
|
||||
record_decl);
|
||||
}
|
||||
} // end namespace Matchers
|
||||
|
||||
auto GetCustomCppTypeMapping(const clang::CXXRecordDecl* record_decl)
|
||||
-> CustomCppTypeMapping {
|
||||
if (Matchers::StdStringView(record_decl)) {
|
||||
return CustomCppTypeMapping::Str;
|
||||
}
|
||||
|
||||
return CustomCppTypeMapping::None;
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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
|
||||
|
||||
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_CUSTOM_TYPE_MAPPING_H_
|
||||
#define CARBON_TOOLCHAIN_CHECK_CPP_CUSTOM_TYPE_MAPPING_H_
|
||||
|
||||
#include "clang/AST/DeclCXX.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Carbon types that have a custom mapping from C++.
|
||||
enum class CustomCppTypeMapping : uint8_t {
|
||||
// None.
|
||||
None,
|
||||
|
||||
// The Carbon `Str` type, which maps to `std::string_view`.
|
||||
Str,
|
||||
};
|
||||
|
||||
// Determines whether record_decl is a C++ class that has a custom mapping into
|
||||
// Carbon, and if so, returns the corresponding Carbon type. Otherwise returns
|
||||
// None.
|
||||
auto GetCustomCppTypeMapping(const clang::CXXRecordDecl* record_decl)
|
||||
-> CustomCppTypeMapping;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
#endif // CARBON_TOOLCHAIN_CHECK_CPP_CUSTOM_TYPE_MAPPING_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
// 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
|
||||
|
||||
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_IMPORT_H_
|
||||
#define CARBON_TOOLCHAIN_CHECK_CPP_IMPORT_H_
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/IntrusiveRefCntPtr.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/VirtualFileSystem.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/check/diagnostic_helpers.h"
|
||||
#include "toolchain/check/operator.h"
|
||||
#include "toolchain/diagnostics/diagnostic_emitter.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Generates a C++ header that includes the imported cpp files, parses it,
|
||||
// generates the AST from it and links `SemIR::File` to it. Report C++ errors
|
||||
// and warnings. If successful, adds a `Cpp` namespace and returns the AST.
|
||||
auto ImportCppFiles(Context& context,
|
||||
llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
|
||||
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
|
||||
std::shared_ptr<clang::CompilerInvocation> invocation)
|
||||
-> std::unique_ptr<clang::ASTUnit>;
|
||||
|
||||
// Imports a function declaration from Clang to Carbon. If successful, returns
|
||||
// the new Carbon function declaration `InstId`. If the declaration was already
|
||||
// imported, returns the mapped instruction.
|
||||
auto ImportCppFunctionDecl(Context& context, SemIR::LocId loc_id,
|
||||
clang::FunctionDecl* clang_decl) -> SemIR::InstId;
|
||||
|
||||
// Looks up the given name in the Clang AST generated when importing C++ code
|
||||
// and returns a lookup result. If using the injected class name (`X.X()`),
|
||||
// imports the class constructor as a function named as the class.
|
||||
auto ImportNameFromCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::NameScopeId scope_id, SemIR::NameId name_id)
|
||||
-> SemIR::ScopeLookupResult;
|
||||
|
||||
// Looks up the given operator in the Clang AST generated when importing C++
|
||||
// code and returns a lookup result.
|
||||
auto ImportOperatorFromCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::NameScopeId scope_id, Operator op)
|
||||
-> SemIR::ScopeLookupResult;
|
||||
|
||||
// Given a Carbon class declaration that was imported from some kind of C++
|
||||
// declaration, such as a class or enum, attempt to import a corresponding class
|
||||
// definition. Returns true if nothing went wrong (whether or not a definition
|
||||
// could be imported), false if a diagnostic was produced.
|
||||
auto ImportClassDefinitionForClangDecl(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ClassId class_id,
|
||||
SemIR::ClangDeclId clang_decl_id)
|
||||
-> bool;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
#endif // CARBON_TOOLCHAIN_CHECK_CPP_IMPORT_H_
|
||||
@@ -0,0 +1,208 @@
|
||||
// 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/overload_resolution.h"
|
||||
|
||||
#include "clang/Sema/Overload.h"
|
||||
#include "clang/Sema/Sema.h"
|
||||
#include "toolchain/check/cpp/import.h"
|
||||
#include "toolchain/check/cpp/type_mapping.h"
|
||||
#include "toolchain/sem_ir/expr_info.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Invents a Clang argument expression to use in overload resolution to
|
||||
// represent the given Carbon argument instruction.
|
||||
static auto InventClangArg(Context& context, SemIR::InstId arg_id)
|
||||
-> clang::Expr* {
|
||||
clang::ExprValueKind value_kind;
|
||||
switch (SemIR::GetExprCategory(context.sem_ir(), arg_id)) {
|
||||
case SemIR::ExprCategory::NotExpr:
|
||||
CARBON_FATAL("Should not see these here");
|
||||
|
||||
case SemIR::ExprCategory::Error:
|
||||
return nullptr;
|
||||
|
||||
case SemIR::ExprCategory::DurableRef:
|
||||
value_kind = clang::ExprValueKind::VK_LValue;
|
||||
break;
|
||||
|
||||
case SemIR::ExprCategory::EphemeralRef:
|
||||
value_kind = clang::ExprValueKind::VK_XValue;
|
||||
break;
|
||||
|
||||
case SemIR::ExprCategory::Value:
|
||||
case SemIR::ExprCategory::Initializing:
|
||||
value_kind = clang::ExprValueKind::VK_PRValue;
|
||||
break;
|
||||
|
||||
case SemIR::ExprCategory::Mixed:
|
||||
// TODO: Handle this by creating an InitListExpr.
|
||||
value_kind = clang::ExprValueKind::VK_PRValue;
|
||||
break;
|
||||
}
|
||||
|
||||
if (context.insts().Get(arg_id).type_id() == SemIR::ErrorInst::TypeId) {
|
||||
// The argument error has already been diagnosed.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
clang::QualType arg_cpp_type = MapToCppType(context, arg_id);
|
||||
if (arg_cpp_type.isNull()) {
|
||||
CARBON_DIAGNOSTIC(CppCallArgTypeNotSupported, Error,
|
||||
"call argument of type {0} is not supported",
|
||||
TypeOfInstId);
|
||||
context.emitter().Emit(arg_id, CppCallArgTypeNotSupported, arg_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: Avoid heap allocating more of these on every call. Either cache them
|
||||
// somewhere or put them on the stack.
|
||||
return new (context.ast_context()) clang::OpaqueValueExpr(
|
||||
// TODO: Add location accordingly.
|
||||
clang::SourceLocation(), arg_cpp_type.getNonReferenceType(), value_kind);
|
||||
}
|
||||
|
||||
// Adds the given overload candidates to the candidate set.
|
||||
static auto AddOverloadCandidataes(clang::Sema& sema,
|
||||
clang::OverloadCandidateSet& candidate_set,
|
||||
const clang::UnresolvedSetImpl& functions,
|
||||
clang::Expr* self_arg,
|
||||
llvm::ArrayRef<clang::Expr*> args) -> void {
|
||||
constexpr bool SuppressUserConversions = false;
|
||||
constexpr bool PartialOverloading = false;
|
||||
constexpr clang::TemplateArgumentListInfo* ExplicitTemplateArgs = nullptr;
|
||||
|
||||
for (auto found_decl : functions.pairs()) {
|
||||
auto* decl = found_decl.getDecl()->getUnderlyingDecl();
|
||||
auto* template_decl = dyn_cast<clang::FunctionTemplateDecl>(decl);
|
||||
auto* fn_decl = template_decl ? template_decl->getTemplatedDecl()
|
||||
: cast<clang::FunctionDecl>(decl);
|
||||
auto* method_decl = dyn_cast<clang::CXXMethodDecl>(fn_decl);
|
||||
if (method_decl && !method_decl->isStatic() &&
|
||||
!isa<clang::CXXConstructorDecl>(fn_decl)) {
|
||||
clang::QualType self_type;
|
||||
clang::Expr::Classification self_classification;
|
||||
if (self_arg) {
|
||||
self_type = self_arg->getType();
|
||||
self_classification = self_arg->Classify(sema.Context);
|
||||
}
|
||||
if (template_decl) {
|
||||
sema.AddMethodTemplateCandidate(
|
||||
template_decl, found_decl,
|
||||
cast<clang::CXXRecordDecl>(template_decl->getDeclContext()),
|
||||
ExplicitTemplateArgs, self_type, self_classification, args,
|
||||
candidate_set, SuppressUserConversions, PartialOverloading);
|
||||
} else {
|
||||
sema.AddMethodCandidate(method_decl, found_decl,
|
||||
method_decl->getParent(), self_type,
|
||||
self_classification, args, candidate_set,
|
||||
SuppressUserConversions, PartialOverloading);
|
||||
}
|
||||
} else {
|
||||
if (template_decl) {
|
||||
sema.AddTemplateOverloadCandidate(
|
||||
template_decl, found_decl, ExplicitTemplateArgs, args,
|
||||
candidate_set, SuppressUserConversions, PartialOverloading);
|
||||
} else {
|
||||
sema.AddOverloadCandidate(fn_decl, found_decl, args, candidate_set,
|
||||
SuppressUserConversions, PartialOverloading);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::CppOverloadSetId overload_set_id,
|
||||
SemIR::InstId self_id,
|
||||
llvm::ArrayRef<SemIR::InstId> arg_ids)
|
||||
-> SemIR::InstId {
|
||||
Diagnostics::AnnotationScope annotate_diagnostics(
|
||||
&context.emitter(), [&](auto& builder) {
|
||||
CARBON_DIAGNOSTIC(InCallToCppFunction, Note,
|
||||
"in call to Cpp function here");
|
||||
builder.Note(loc_id, InCallToCppFunction);
|
||||
});
|
||||
|
||||
// Map Carbon call argument types to C++ types.
|
||||
clang::Expr* self_expr = nullptr;
|
||||
if (self_id.has_value()) {
|
||||
self_expr = InventClangArg(context, self_id);
|
||||
if (!self_expr) {
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
}
|
||||
llvm::SmallVector<clang::Expr*> arg_exprs;
|
||||
arg_exprs.reserve(arg_ids.size());
|
||||
for (SemIR::InstId arg_id : arg_ids) {
|
||||
auto* arg_expr = InventClangArg(context, arg_id);
|
||||
if (!arg_expr) {
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
arg_exprs.push_back(arg_expr);
|
||||
}
|
||||
|
||||
const SemIR::CppOverloadSet& overload_set =
|
||||
context.cpp_overload_sets().Get(overload_set_id);
|
||||
|
||||
// Add candidate functions from the name lookup.
|
||||
clang::OverloadCandidateSet candidate_set(
|
||||
// TODO: Add location accordingly.
|
||||
clang::SourceLocation(),
|
||||
clang::OverloadCandidateSet::CandidateSetKind::CSK_Normal);
|
||||
|
||||
clang::ASTUnit* ast = context.sem_ir().clang_ast_unit();
|
||||
CARBON_CHECK(ast);
|
||||
clang::Sema& sema = ast->getSema();
|
||||
|
||||
AddOverloadCandidataes(sema, candidate_set, overload_set.candidate_functions,
|
||||
self_expr, arg_exprs);
|
||||
|
||||
// Find best viable function among the candidates.
|
||||
clang::OverloadCandidateSet::iterator best_viable_fn;
|
||||
clang::OverloadingResult overloading_result =
|
||||
// TODO: Add location accordingly.
|
||||
candidate_set.BestViableFunction(sema, clang::SourceLocation(),
|
||||
best_viable_fn);
|
||||
|
||||
switch (overloading_result) {
|
||||
case clang::OverloadingResult::OR_Success: {
|
||||
// TODO: Handle the cases when Function is null.
|
||||
CARBON_CHECK(best_viable_fn->Function);
|
||||
sema.MarkFunctionReferenced(clang::SourceLocation(),
|
||||
best_viable_fn->Function);
|
||||
SemIR::InstId result =
|
||||
ImportCppFunctionDecl(context, loc_id, best_viable_fn->Function);
|
||||
return result;
|
||||
}
|
||||
case clang::OverloadingResult::OR_No_Viable_Function: {
|
||||
// TODO: Add notes with the candidates.
|
||||
CARBON_DIAGNOSTIC(CppOverloadingNoViableFunctionFound, Error,
|
||||
"no matching function for call to `{0}`",
|
||||
SemIR::NameId);
|
||||
context.emitter().Emit(loc_id, CppOverloadingNoViableFunctionFound,
|
||||
overload_set.name_id);
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
case clang::OverloadingResult::OR_Ambiguous: {
|
||||
// TODO: Add notes with the candidates.
|
||||
CARBON_DIAGNOSTIC(CppOverloadingAmbiguousCandidatesFound, Error,
|
||||
"call to `{0}` is ambiguous", SemIR::NameId);
|
||||
context.emitter().Emit(loc_id, CppOverloadingAmbiguousCandidatesFound,
|
||||
overload_set.name_id);
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
case clang::OverloadingResult::OR_Deleted: {
|
||||
// TODO: Add notes with the candidates.
|
||||
CARBON_DIAGNOSTIC(CppOverloadingDeletedFunctionFound, Error,
|
||||
"call to deleted function `{0}`", SemIR::NameId);
|
||||
context.emitter().Emit(loc_id, CppOverloadingDeletedFunctionFound,
|
||||
overload_set.name_id);
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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
|
||||
|
||||
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_OVERLOAD_RESOLUTION_H_
|
||||
#define CARBON_TOOLCHAIN_CHECK_CPP_OVERLOAD_RESOLUTION_H_
|
||||
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Performs overloading resolution for a call to an overloaded C++ set. A set
|
||||
// with a single non-templated function goes through the same rules for
|
||||
// overloading resolution. Uses Clang to find the best viable function for the
|
||||
// call. Returns the resolved function, or an error instruction if overload
|
||||
// resolution failed.
|
||||
//
|
||||
// Note on non-overloaded functions: In C++, a single non-templated function is
|
||||
// also treated as an overloaded set and goes through the overload resolution to
|
||||
// ensure that the function is viable for the call. This is to make sure that
|
||||
// calls that have no viable implicit conversion sequence are rejected even when
|
||||
// an implicit conversion is possible. Keeping the same behavior here for
|
||||
// consistency and supporting migrations so that the migrated callers from C++
|
||||
// remain valid.
|
||||
auto PerformCppOverloadResolution(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::CppOverloadSetId overload_set_id,
|
||||
SemIR::InstId self_id,
|
||||
llvm::ArrayRef<SemIR::InstId> arg_ids)
|
||||
-> SemIR::InstId;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
#endif // CARBON_TOOLCHAIN_CHECK_CPP_OVERLOAD_RESOLUTION_H_
|
||||
@@ -0,0 +1,656 @@
|
||||
// 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/thunk.h"
|
||||
|
||||
#include "clang/AST/GlobalDecl.h"
|
||||
#include "clang/AST/Mangle.h"
|
||||
#include "clang/Sema/Lookup.h"
|
||||
#include "clang/Sema/Overload.h"
|
||||
#include "clang/Sema/Sema.h"
|
||||
#include "toolchain/check/call.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/check/control_flow.h"
|
||||
#include "toolchain/check/convert.h"
|
||||
#include "toolchain/check/literal.h"
|
||||
#include "toolchain/check/type.h"
|
||||
#include "toolchain/check/type_completion.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Returns the GlobalDecl to use to represent the given function declaration.
|
||||
// TODO: Refactor with `Lower::CreateGlobalDecl`.
|
||||
static auto GetGlobalDecl(const clang::FunctionDecl* decl)
|
||||
-> clang::GlobalDecl {
|
||||
if (const auto* ctor = dyn_cast<clang::CXXConstructorDecl>(decl)) {
|
||||
return clang::GlobalDecl(ctor, clang::CXXCtorType::Ctor_Complete);
|
||||
}
|
||||
return clang::GlobalDecl(decl);
|
||||
}
|
||||
|
||||
// Returns the C++ thunk mangled name given the callee function.
|
||||
static auto GenerateThunkMangledName(
|
||||
clang::MangleContext& mangle_context,
|
||||
const clang::FunctionDecl& callee_function_decl) -> std::string {
|
||||
RawStringOstream mangled_name_stream;
|
||||
mangle_context.mangleName(GetGlobalDecl(&callee_function_decl),
|
||||
mangled_name_stream);
|
||||
mangled_name_stream << ".carbon_thunk";
|
||||
|
||||
return mangled_name_stream.TakeStr();
|
||||
}
|
||||
|
||||
// Returns true if a C++ thunk is required for the given type. A C++ thunk is
|
||||
// required for any type except for void, pointer types and signed 32-bit and
|
||||
// 64-bit integers.
|
||||
static auto IsThunkRequiredForType(Context& context, SemIR::TypeId type_id)
|
||||
-> bool {
|
||||
if (!type_id.has_value() || type_id == SemIR::ErrorInst::TypeId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
type_id = context.types().GetUnqualifiedType(type_id);
|
||||
|
||||
switch (context.types().GetAsInst(type_id).kind()) {
|
||||
case SemIR::PointerType::Kind: {
|
||||
return false;
|
||||
}
|
||||
|
||||
case SemIR::ClassType::Kind: {
|
||||
if (!context.types().IsComplete(type_id)) {
|
||||
// Signed integers of 32 or 64 bits should be completed when imported.
|
||||
return true;
|
||||
}
|
||||
|
||||
auto int_info = context.types().TryGetIntTypeInfo(type_id);
|
||||
if (!int_info || !int_info->bit_width.has_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
llvm::APInt bit_width = context.ints().Get(int_info->bit_width);
|
||||
return bit_width != 32 && bit_width != 64;
|
||||
}
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto IsCppThunkRequired(Context& context, const SemIR::Function& function)
|
||||
-> bool {
|
||||
if (!function.clang_decl_id.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A thunk is required if any parameter or return type requires it. However,
|
||||
// we don't generate a thunk if any relevant type is erroneous.
|
||||
bool thunk_required = false;
|
||||
|
||||
// We require a thunk if any parameter is of reference type, even if the
|
||||
// corresponding SemIR function has an acceptable parameter type.
|
||||
// TODO: We should be able to avoid thunks for reference parameters.
|
||||
const auto* decl = cast<clang::FunctionDecl>(
|
||||
context.sem_ir().clang_decls().Get(function.clang_decl_id).decl);
|
||||
for (auto* param : decl->parameters()) {
|
||||
if (param->getType()->isReferenceType()) {
|
||||
thunk_required = true;
|
||||
}
|
||||
}
|
||||
|
||||
SemIR::TypeId return_type_id =
|
||||
function.GetDeclaredReturnType(context.sem_ir());
|
||||
if (return_type_id.has_value()) {
|
||||
if (return_type_id == SemIR::ErrorInst::TypeId) {
|
||||
return false;
|
||||
}
|
||||
thunk_required = IsThunkRequiredForType(context, return_type_id);
|
||||
}
|
||||
|
||||
for (auto param_id :
|
||||
context.inst_blocks().GetOrEmpty(function.call_params_id)) {
|
||||
if (param_id == SemIR::ErrorInst::InstId) {
|
||||
return false;
|
||||
}
|
||||
if (!thunk_required &&
|
||||
IsThunkRequiredForType(
|
||||
context,
|
||||
context.insts().GetAs<SemIR::AnyParam>(param_id).type_id)) {
|
||||
thunk_required = true;
|
||||
}
|
||||
}
|
||||
|
||||
return thunk_required;
|
||||
}
|
||||
|
||||
// Returns whether the type is void, a pointer, or a signed int of 32 or 64
|
||||
// bits.
|
||||
static auto IsSimpleAbiType(clang::ASTContext& ast_context,
|
||||
clang::QualType type) -> bool {
|
||||
if (type->isVoidType() || type->isPointerType()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (const auto* builtin_type = type->getAs<clang::BuiltinType>()) {
|
||||
if (builtin_type->isSignedInteger()) {
|
||||
uint64_t type_size = ast_context.getIntWidth(type);
|
||||
return type_size == 32 || type_size == 64;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Information about the callee of a thunk.
|
||||
struct CalleeFunctionInfo {
|
||||
explicit CalleeFunctionInfo(clang::FunctionDecl* decl) : decl(decl) {
|
||||
auto& ast_context = decl->getASTContext();
|
||||
const auto* method_decl = dyn_cast<clang::CXXMethodDecl>(decl);
|
||||
bool is_ctor = isa<clang::CXXConstructorDecl>(decl);
|
||||
has_object_parameter = method_decl && !method_decl->isStatic() && !is_ctor;
|
||||
if (has_object_parameter && method_decl->isImplicitObjectMemberFunction()) {
|
||||
implicit_this_type = method_decl->getThisType();
|
||||
}
|
||||
effective_return_type =
|
||||
is_ctor ? ast_context.getCanonicalTagType(method_decl->getParent())
|
||||
: decl->getReturnType();
|
||||
has_simple_return_type =
|
||||
IsSimpleAbiType(ast_context, effective_return_type);
|
||||
}
|
||||
|
||||
// Returns whether this callee has an implicit `this` parameter.
|
||||
auto has_implicit_object_parameter() const -> bool {
|
||||
return !implicit_this_type.isNull();
|
||||
}
|
||||
|
||||
// Returns whether this callee has an explicit `this` parameter.
|
||||
auto has_explicit_object_parameter() const -> bool {
|
||||
return has_object_parameter && !has_implicit_object_parameter();
|
||||
}
|
||||
|
||||
// Returns the number of parameters the thunk should have.
|
||||
auto num_thunk_params() const -> unsigned {
|
||||
return has_implicit_object_parameter() + decl->getNumParams() +
|
||||
!has_simple_return_type;
|
||||
}
|
||||
|
||||
// Returns the thunk parameter index corresponding to a given callee parameter
|
||||
// index.
|
||||
auto GetThunkParamIndex(unsigned callee_param_index) const -> unsigned {
|
||||
return has_implicit_object_parameter() + callee_param_index;
|
||||
}
|
||||
|
||||
// Returns the thunk parameter index corresponding to the parameter that holds
|
||||
// the address of the return value.
|
||||
auto GetThunkReturnParamIndex() const -> unsigned {
|
||||
CARBON_CHECK(!has_simple_return_type);
|
||||
return has_implicit_object_parameter() + decl->getNumParams();
|
||||
}
|
||||
|
||||
// The callee function.
|
||||
clang::FunctionDecl* decl;
|
||||
|
||||
// Whether the callee has an object parameter, which might be explicit or
|
||||
// implicit.
|
||||
bool has_object_parameter;
|
||||
|
||||
// If the callee has an implicit object parameter, the corresponding `this`
|
||||
// type. Otherwise a null type.
|
||||
clang::QualType implicit_this_type;
|
||||
|
||||
// The return type that the callee has when viewed from Carbon. This is the
|
||||
// C++ return type, except that constructors return the class type in Carbon
|
||||
// and return void in Clang's AST.
|
||||
clang::QualType effective_return_type;
|
||||
|
||||
// Whether the callee has a simple return type, that we can return directly.
|
||||
// If not, we'll return through an out parameter instead.
|
||||
bool has_simple_return_type;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Given a pointer type, returns the corresponding _Nonnull-qualified pointer
|
||||
// type.
|
||||
static auto GetNonnullType(clang::ASTContext& ast_context,
|
||||
clang::QualType pointer_type) -> clang::QualType {
|
||||
return ast_context.getAttributedType(clang::NullabilityKind::NonNull,
|
||||
pointer_type, pointer_type);
|
||||
}
|
||||
|
||||
// Given the type of a callee parameter, returns the type to use for the
|
||||
// corresponding thunk parameter.
|
||||
static auto GetThunkParameterType(clang::ASTContext& ast_context,
|
||||
clang::QualType callee_type)
|
||||
-> clang::QualType {
|
||||
if (IsSimpleAbiType(ast_context, callee_type)) {
|
||||
return callee_type;
|
||||
}
|
||||
return GetNonnullType(ast_context, ast_context.getPointerType(
|
||||
callee_type.getNonReferenceType()));
|
||||
}
|
||||
|
||||
// Creates the thunk parameter types given the callee function.
|
||||
static auto BuildThunkParameterTypes(clang::ASTContext& ast_context,
|
||||
CalleeFunctionInfo callee_info)
|
||||
-> llvm::SmallVector<clang::QualType> {
|
||||
llvm::SmallVector<clang::QualType> thunk_param_types;
|
||||
thunk_param_types.reserve(callee_info.num_thunk_params());
|
||||
if (callee_info.has_implicit_object_parameter()) {
|
||||
thunk_param_types.push_back(
|
||||
GetNonnullType(ast_context, callee_info.implicit_this_type));
|
||||
}
|
||||
|
||||
for (const clang::ParmVarDecl* callee_param :
|
||||
callee_info.decl->parameters()) {
|
||||
// TODO: We should use the type from the function signature, not the type of
|
||||
// the parameter here.
|
||||
thunk_param_types.push_back(
|
||||
GetThunkParameterType(ast_context, callee_param->getType()));
|
||||
}
|
||||
|
||||
if (!callee_info.has_simple_return_type) {
|
||||
thunk_param_types.push_back(GetNonnullType(
|
||||
ast_context,
|
||||
ast_context.getPointerType(callee_info.effective_return_type)));
|
||||
}
|
||||
|
||||
CARBON_CHECK(thunk_param_types.size() == callee_info.num_thunk_params());
|
||||
return thunk_param_types;
|
||||
}
|
||||
|
||||
// Returns the thunk parameters using the callee function parameter identifiers.
|
||||
static auto BuildThunkParameters(clang::ASTContext& ast_context,
|
||||
CalleeFunctionInfo callee_info,
|
||||
clang::FunctionDecl* thunk_function_decl)
|
||||
-> llvm::SmallVector<clang::ParmVarDecl*> {
|
||||
clang::SourceLocation clang_loc = callee_info.decl->getLocation();
|
||||
|
||||
const auto* thunk_function_proto_type =
|
||||
thunk_function_decl->getFunctionType()->getAs<clang::FunctionProtoType>();
|
||||
|
||||
llvm::SmallVector<clang::ParmVarDecl*> thunk_params;
|
||||
unsigned num_thunk_params = thunk_function_decl->getNumParams();
|
||||
thunk_params.reserve(num_thunk_params);
|
||||
|
||||
if (callee_info.has_implicit_object_parameter()) {
|
||||
clang::ParmVarDecl* thunk_param =
|
||||
clang::ParmVarDecl::Create(ast_context, thunk_function_decl, clang_loc,
|
||||
clang_loc, &ast_context.Idents.get("this"),
|
||||
thunk_function_proto_type->getParamType(0),
|
||||
nullptr, clang::SC_None, nullptr);
|
||||
thunk_params.push_back(thunk_param);
|
||||
}
|
||||
|
||||
for (unsigned i : llvm::seq(callee_info.decl->getNumParams())) {
|
||||
clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create(
|
||||
ast_context, thunk_function_decl, clang_loc, clang_loc,
|
||||
callee_info.decl->getParamDecl(i)->getIdentifier(),
|
||||
thunk_function_proto_type->getParamType(
|
||||
callee_info.GetThunkParamIndex(i)),
|
||||
nullptr, clang::SC_None, nullptr);
|
||||
thunk_params.push_back(thunk_param);
|
||||
}
|
||||
|
||||
if (!callee_info.has_simple_return_type) {
|
||||
clang::ParmVarDecl* thunk_param =
|
||||
clang::ParmVarDecl::Create(ast_context, thunk_function_decl, clang_loc,
|
||||
clang_loc, &ast_context.Idents.get("return"),
|
||||
thunk_function_proto_type->getParamType(
|
||||
callee_info.GetThunkReturnParamIndex()),
|
||||
nullptr, clang::SC_None, nullptr);
|
||||
thunk_params.push_back(thunk_param);
|
||||
}
|
||||
|
||||
CARBON_CHECK(thunk_params.size() == num_thunk_params);
|
||||
return thunk_params;
|
||||
}
|
||||
|
||||
// Returns the thunk function declaration given the callee function and the
|
||||
// thunk parameter types.
|
||||
static auto CreateThunkFunctionDecl(
|
||||
Context& context, CalleeFunctionInfo callee_info,
|
||||
llvm::ArrayRef<clang::QualType> thunk_param_types) -> clang::FunctionDecl* {
|
||||
clang::ASTContext& ast_context = context.ast_context();
|
||||
clang::SourceLocation clang_loc = callee_info.decl->getLocation();
|
||||
|
||||
clang::IdentifierInfo& identifier_info = ast_context.Idents.get(
|
||||
callee_info.decl->getNameAsString() + "__carbon_thunk");
|
||||
|
||||
auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
|
||||
clang::QualType thunk_function_type = ast_context.getFunctionType(
|
||||
callee_info.has_simple_return_type ? callee_info.effective_return_type
|
||||
: ast_context.VoidTy,
|
||||
thunk_param_types, ext_proto_info);
|
||||
|
||||
clang::DeclContext* decl_context = ast_context.getTranslationUnitDecl();
|
||||
// TODO: Thunks should not have external linkage, consider using `SC_Static`.
|
||||
clang::FunctionDecl* thunk_function_decl = clang::FunctionDecl::Create(
|
||||
ast_context, decl_context, clang_loc, clang_loc,
|
||||
clang::DeclarationName(&identifier_info), thunk_function_type,
|
||||
/*TInfo=*/nullptr, clang::SC_Extern);
|
||||
decl_context->addDecl(thunk_function_decl);
|
||||
|
||||
thunk_function_decl->setParams(
|
||||
BuildThunkParameters(ast_context, callee_info, thunk_function_decl));
|
||||
|
||||
// Set always_inline.
|
||||
thunk_function_decl->addAttr(
|
||||
clang::AlwaysInlineAttr::CreateImplicit(ast_context));
|
||||
|
||||
// Set asm("<callee function mangled name>.carbon_thunk").
|
||||
thunk_function_decl->addAttr(clang::AsmLabelAttr::CreateImplicit(
|
||||
ast_context,
|
||||
GenerateThunkMangledName(*context.sem_ir().clang_mangle_context(),
|
||||
*callee_info.decl),
|
||||
clang_loc));
|
||||
|
||||
// Set function declaration type source info.
|
||||
thunk_function_decl->setTypeSourceInfo(ast_context.getTrivialTypeSourceInfo(
|
||||
thunk_function_decl->getType(), clang_loc));
|
||||
|
||||
return thunk_function_decl;
|
||||
}
|
||||
|
||||
// Builds a reference to the given parameter thunk. If `type` is specified, that
|
||||
// is the callee parameter type that's being held by the parameter, and
|
||||
// conversions will be performed as necessary to recover a value of that type.
|
||||
static auto BuildThunkParamRef(clang::Sema& sema,
|
||||
clang::FunctionDecl* thunk_function_decl,
|
||||
unsigned thunk_index,
|
||||
clang::QualType type = clang::QualType())
|
||||
-> clang::Expr* {
|
||||
clang::ParmVarDecl* thunk_param =
|
||||
thunk_function_decl->getParamDecl(thunk_index);
|
||||
clang::SourceLocation clang_loc = thunk_param->getLocation();
|
||||
|
||||
clang::Expr* call_arg = sema.BuildDeclRefExpr(
|
||||
thunk_param, thunk_param->getType().getNonReferenceType(),
|
||||
clang::VK_LValue, clang_loc);
|
||||
if (!type.isNull() && thunk_param->getType() != type) {
|
||||
clang::ExprResult deref_result =
|
||||
sema.BuildUnaryOp(nullptr, clang_loc, clang::UO_Deref, call_arg);
|
||||
CARBON_CHECK(deref_result.isUsable());
|
||||
|
||||
// Cast to an rvalue when initializing an rvalue reference. The validity of
|
||||
// the initialization of the reference should be validated by the caller of
|
||||
// the thunk.
|
||||
//
|
||||
// TODO: Consider inserting a cast to an rvalue in more cases. Note that we
|
||||
// currently pass pointers to non-temporary objects as the argument when
|
||||
// calling a thunk, so we'll need to either change that or generate
|
||||
// different thunks depending on whether we're moving from each parameter.
|
||||
if (type->isRValueReferenceType()) {
|
||||
deref_result = clang::ImplicitCastExpr::Create(
|
||||
sema.getASTContext(), deref_result.get()->getType(), clang::CK_NoOp,
|
||||
deref_result.get(), nullptr, clang::ExprValueKind::VK_XValue,
|
||||
clang::FPOptionsOverride());
|
||||
}
|
||||
call_arg = deref_result.get();
|
||||
}
|
||||
return call_arg;
|
||||
}
|
||||
|
||||
// Builds a reference to the parameter thunk parameter corresponding to the
|
||||
// given callee parameter index.
|
||||
static auto BuildParamRefForCalleeArg(clang::Sema& sema,
|
||||
clang::FunctionDecl* thunk_function_decl,
|
||||
CalleeFunctionInfo callee_info,
|
||||
unsigned callee_index) -> clang::Expr* {
|
||||
unsigned thunk_index = callee_info.GetThunkParamIndex(callee_index);
|
||||
return BuildThunkParamRef(
|
||||
sema, thunk_function_decl, thunk_index,
|
||||
callee_info.decl->getParamDecl(callee_index)->getType());
|
||||
}
|
||||
|
||||
// Builds an argument list for the callee function by creating suitable uses of
|
||||
// the corresponding thunk parameters.
|
||||
static auto BuildCalleeArgs(clang::Sema& sema,
|
||||
clang::FunctionDecl* thunk_function_decl,
|
||||
CalleeFunctionInfo callee_info)
|
||||
-> llvm::SmallVector<clang::Expr*> {
|
||||
llvm::SmallVector<clang::Expr*> call_args;
|
||||
// The object parameter is always passed as `self`, not in the callee argument
|
||||
// list, so the first argument corresponds to the second parameter if there is
|
||||
// an explicit object parameter and the first parameter otherwise.
|
||||
unsigned first_param = callee_info.has_explicit_object_parameter();
|
||||
unsigned num_params = callee_info.decl->getNumParams();
|
||||
call_args.reserve(num_params - first_param);
|
||||
for (unsigned callee_index : llvm::seq(first_param, num_params)) {
|
||||
call_args.push_back(BuildParamRefForCalleeArg(sema, thunk_function_decl,
|
||||
callee_info, callee_index));
|
||||
}
|
||||
return call_args;
|
||||
}
|
||||
|
||||
// Builds the thunk function body which calls the callee function using the call
|
||||
// args and returns the callee function return value. Returns nullptr on
|
||||
// failure.
|
||||
static auto BuildThunkBody(clang::Sema& sema,
|
||||
clang::FunctionDecl* thunk_function_decl,
|
||||
CalleeFunctionInfo callee_info)
|
||||
-> clang::StmtResult {
|
||||
// TODO: Consider building a CompoundStmt holding our created statement to
|
||||
// make our result more closely resemble a real C++ function.
|
||||
|
||||
clang::SourceLocation clang_loc = callee_info.decl->getLocation();
|
||||
|
||||
// If the callee has an object parameter, build a member access expression as
|
||||
// the callee. Otherwise, build a regular reference to the function.
|
||||
clang::ExprResult callee;
|
||||
if (callee_info.has_object_parameter) {
|
||||
auto* object_param_ref =
|
||||
BuildThunkParamRef(sema, thunk_function_decl, 0,
|
||||
callee_info.has_explicit_object_parameter()
|
||||
? callee_info.decl->getParamDecl(0)->getType()
|
||||
: clang::QualType());
|
||||
bool is_arrow = callee_info.has_implicit_object_parameter();
|
||||
auto object =
|
||||
sema.PerformMemberExprBaseConversion(object_param_ref, is_arrow);
|
||||
if (object.isInvalid()) {
|
||||
return clang::StmtError();
|
||||
}
|
||||
callee = sema.BuildMemberExpr(
|
||||
object.get(), is_arrow, clang_loc, clang::NestedNameSpecifierLoc(),
|
||||
clang::SourceLocation(), callee_info.decl,
|
||||
clang::DeclAccessPair::make(callee_info.decl, clang::AS_public),
|
||||
/*HadMultipleCandidates=*/false, clang::DeclarationNameInfo(),
|
||||
sema.getASTContext().BoundMemberTy, clang::VK_PRValue,
|
||||
clang::OK_Ordinary);
|
||||
} else if (!isa<clang::CXXConstructorDecl>(callee_info.decl)) {
|
||||
callee =
|
||||
sema.BuildDeclRefExpr(callee_info.decl, callee_info.decl->getType(),
|
||||
clang::VK_PRValue, clang_loc);
|
||||
}
|
||||
|
||||
if (callee.isInvalid()) {
|
||||
return clang::StmtError();
|
||||
}
|
||||
|
||||
// Build the argument list.
|
||||
llvm::SmallVector<clang::Expr*> call_args =
|
||||
BuildCalleeArgs(sema, thunk_function_decl, callee_info);
|
||||
|
||||
clang::ExprResult call;
|
||||
if (auto info = clang::getConstructorInfo(callee_info.decl);
|
||||
info.Constructor) {
|
||||
// In C++, there are no direct calls to constructors, only initialization,
|
||||
// so we need to type-check and build the call ourselves.
|
||||
auto type = sema.Context.getCanonicalTagType(
|
||||
cast<clang::CXXRecordDecl>(callee_info.decl->getParent()));
|
||||
llvm::SmallVector<clang::Expr*> converted_args;
|
||||
converted_args.reserve(call_args.size());
|
||||
if (sema.CompleteConstructorCall(info.Constructor, type, call_args,
|
||||
clang_loc, converted_args)) {
|
||||
return clang::StmtError();
|
||||
}
|
||||
call = sema.BuildCXXConstructExpr(
|
||||
clang_loc, type, callee_info.decl, info.Constructor, converted_args,
|
||||
false, false, false, false, clang::CXXConstructionKind::Complete,
|
||||
clang_loc);
|
||||
} else {
|
||||
call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc, call_args,
|
||||
clang_loc);
|
||||
}
|
||||
if (!call.isUsable()) {
|
||||
return clang::StmtError();
|
||||
}
|
||||
|
||||
if (callee_info.has_simple_return_type) {
|
||||
return sema.BuildReturnStmt(clang_loc, call.get());
|
||||
}
|
||||
|
||||
auto* return_object_addr = BuildThunkParamRef(
|
||||
sema, thunk_function_decl, callee_info.GetThunkReturnParamIndex());
|
||||
auto return_type = callee_info.effective_return_type;
|
||||
auto* return_type_info =
|
||||
sema.Context.getTrivialTypeSourceInfo(return_type, clang_loc);
|
||||
auto placement_new = sema.BuildCXXNew(
|
||||
clang_loc, /*UseGlobal=*/true, clang_loc, {return_object_addr}, clang_loc,
|
||||
/*TypeIdParens=*/clang::SourceRange(), return_type, return_type_info,
|
||||
/*ArraySize=*/std::nullopt, clang_loc, call.get());
|
||||
return sema.ActOnExprStmt(placement_new, /*DiscardedValue=*/true);
|
||||
}
|
||||
|
||||
auto BuildCppThunk(Context& context, const SemIR::Function& callee_function)
|
||||
-> clang::FunctionDecl* {
|
||||
clang::FunctionDecl* callee_function_decl =
|
||||
context.sem_ir()
|
||||
.clang_decls()
|
||||
.Get(callee_function.clang_decl_id)
|
||||
.decl->getAsFunction();
|
||||
CARBON_CHECK(callee_function_decl);
|
||||
|
||||
CalleeFunctionInfo callee_info(callee_function_decl);
|
||||
|
||||
// Build the thunk function declaration.
|
||||
auto thunk_param_types =
|
||||
BuildThunkParameterTypes(context.ast_context(), callee_info);
|
||||
clang::FunctionDecl* thunk_function_decl =
|
||||
CreateThunkFunctionDecl(context, callee_info, thunk_param_types);
|
||||
|
||||
// Build the thunk function body.
|
||||
clang::Sema& sema = context.sem_ir().clang_ast_unit()->getSema();
|
||||
clang::Sema::ContextRAII context_raii(sema, thunk_function_decl);
|
||||
sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl);
|
||||
clang::StmtResult body =
|
||||
BuildThunkBody(sema, thunk_function_decl, callee_info);
|
||||
sema.ActOnFinishFunctionBody(thunk_function_decl, body.get());
|
||||
if (body.isInvalid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return thunk_function_decl;
|
||||
}
|
||||
|
||||
auto PerformCppThunkCall(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::FunctionId callee_function_id,
|
||||
llvm::ArrayRef<SemIR::InstId> callee_arg_ids,
|
||||
SemIR::InstId thunk_callee_id) -> SemIR::InstId {
|
||||
auto& callee_function = context.functions().Get(callee_function_id);
|
||||
auto callee_function_params =
|
||||
context.inst_blocks().Get(callee_function.call_params_id);
|
||||
|
||||
auto thunk_callee = GetCalleeFunction(context.sem_ir(), thunk_callee_id);
|
||||
auto& thunk_function = context.functions().Get(thunk_callee.function_id);
|
||||
auto thunk_function_params =
|
||||
context.inst_blocks().Get(thunk_function.call_params_id);
|
||||
|
||||
// Whether we need to pass a return address to the thunk as a final argument.
|
||||
bool thunk_takes_return_address =
|
||||
callee_function.return_slot_pattern_id.has_value() &&
|
||||
!thunk_function.return_slot_pattern_id.has_value();
|
||||
|
||||
// The number of arguments we should be acquiring in order to call the thunk.
|
||||
// This includes the return address parameter, if any.
|
||||
unsigned num_thunk_args =
|
||||
context.inst_blocks().Get(thunk_function.param_patterns_id).size();
|
||||
|
||||
// The corresponding number of arguments that would be provided in a syntactic
|
||||
// call to the callee. This excludes the return slot.
|
||||
unsigned num_callee_args = num_thunk_args - thunk_takes_return_address;
|
||||
|
||||
// Grab the return slot argument, if we were given one.
|
||||
auto return_slot_id = SemIR::InstId::None;
|
||||
if (callee_arg_ids.size() == num_callee_args + 1) {
|
||||
return_slot_id = callee_arg_ids.consume_back();
|
||||
}
|
||||
|
||||
// If there's a return slot pattern, drop the corresponding parameter.
|
||||
// TODO: The parameter should probably only be created if the return pattern
|
||||
// actually needs a return address to be passed in.
|
||||
if (thunk_function.return_slot_pattern_id.has_value()) {
|
||||
thunk_function_params.consume_back();
|
||||
}
|
||||
if (callee_function.return_slot_pattern_id.has_value()) {
|
||||
callee_function_params.consume_back();
|
||||
}
|
||||
|
||||
// We assume that the call parameters exactly match the parameter patterns for
|
||||
// both the thunk and the callee. This is currently guaranteed because we only
|
||||
// create trivial *ParamPatterns when importing a C++ function.
|
||||
CARBON_CHECK(num_callee_args == callee_function_params.size());
|
||||
CARBON_CHECK(num_callee_args == callee_arg_ids.size());
|
||||
CARBON_CHECK(num_thunk_args == thunk_function_params.size());
|
||||
|
||||
// Build the thunk arguments by converting the callee arguments as needed.
|
||||
llvm::SmallVector<SemIR::InstId> thunk_arg_ids;
|
||||
thunk_arg_ids.reserve(num_thunk_args);
|
||||
for (auto [callee_param_inst_id, thunk_param_inst_id, callee_arg_id] :
|
||||
llvm::zip(callee_function_params, thunk_function_params,
|
||||
callee_arg_ids)) {
|
||||
SemIR::TypeId callee_param_type_id =
|
||||
context.insts().GetAs<SemIR::AnyParam>(callee_param_inst_id).type_id;
|
||||
SemIR::TypeId thunk_param_type_id =
|
||||
context.insts().GetAs<SemIR::AnyParam>(thunk_param_inst_id).type_id;
|
||||
|
||||
SemIR::InstId arg_id = callee_arg_id;
|
||||
if (callee_param_type_id != thunk_param_type_id) {
|
||||
arg_id = Convert(context, loc_id, arg_id,
|
||||
{.kind = ConversionTarget::CppThunkRef,
|
||||
.type_id = callee_param_type_id});
|
||||
arg_id = AddInst<SemIR::AddrOf>(
|
||||
context, loc_id,
|
||||
{.type_id = GetPointerType(
|
||||
context, context.types().GetInstId(callee_param_type_id)),
|
||||
.lvalue_id = arg_id});
|
||||
arg_id =
|
||||
ConvertToValueOfType(context, loc_id, arg_id, thunk_param_type_id);
|
||||
}
|
||||
thunk_arg_ids.push_back(arg_id);
|
||||
}
|
||||
|
||||
// Add an argument to hold the result of the call, if necessary.
|
||||
auto return_type_id = callee_function.GetDeclaredReturnType(context.sem_ir());
|
||||
if (thunk_takes_return_address) {
|
||||
// Create a temporary if the caller didn't provide a return slot.
|
||||
if (!return_slot_id.has_value()) {
|
||||
return_slot_id = AddInst<SemIR::TemporaryStorage>(
|
||||
context, loc_id, {.type_id = return_type_id});
|
||||
}
|
||||
|
||||
auto arg_id = AddInst<SemIR::AddrOf>(
|
||||
context, loc_id,
|
||||
{.type_id = GetPointerType(
|
||||
context, context.types().GetInstId(
|
||||
context.insts().Get(return_slot_id).type_id())),
|
||||
.lvalue_id = return_slot_id});
|
||||
thunk_arg_ids.push_back(arg_id);
|
||||
}
|
||||
|
||||
auto result_id = PerformCall(context, loc_id, thunk_callee_id, thunk_arg_ids);
|
||||
|
||||
// Produce the result of the call, taking the value from the return storage.
|
||||
if (thunk_takes_return_address) {
|
||||
result_id = AddInst<SemIR::InPlaceInit>(context, loc_id,
|
||||
{.type_id = return_type_id,
|
||||
.src_id = result_id,
|
||||
.dest_id = return_slot_id});
|
||||
}
|
||||
|
||||
return result_id;
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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
|
||||
|
||||
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_THUNK_H_
|
||||
#define CARBON_TOOLCHAIN_CHECK_CPP_THUNK_H_
|
||||
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Returns whether the given C++ imported function requires a C++ thunk to be
|
||||
// used to call it. A C++ thunk is required for functions that use any type
|
||||
// except void, pointer types and signed 32-bit and 64-bit integers.
|
||||
auto IsCppThunkRequired(Context& context, const SemIR::Function& function)
|
||||
-> bool;
|
||||
|
||||
// Given a function signature and a callee function, builds a C++ thunk with
|
||||
// simple ABI (pointers, i32 and i64 types) that calls the specified callee.
|
||||
// Assumes `IsCppThunkRequired()` return true for `callee_function`. Returns
|
||||
// `nullptr` on failure.
|
||||
auto BuildCppThunk(Context& context, const SemIR::Function& callee_function)
|
||||
-> clang::FunctionDecl*;
|
||||
|
||||
// Builds a call to a thunk function that forwards a call argument list built
|
||||
// for `callee_function_id` to a call to `thunk_callee_id`, for use when
|
||||
// building a call from a C++ thunk to its target. This is like `PerformCall`,
|
||||
// except that it takes a list of call arguments for `callee_function_id`, not a
|
||||
// syntactic argument list.
|
||||
auto PerformCppThunkCall(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::FunctionId callee_function_id,
|
||||
llvm::ArrayRef<SemIR::InstId> callee_arg_ids,
|
||||
SemIR::InstId thunk_callee_id) -> SemIR::InstId;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
#endif // CARBON_TOOLCHAIN_CHECK_CPP_THUNK_H_
|
||||
@@ -0,0 +1,227 @@
|
||||
// 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/type_mapping.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
#include "clang/AST/Type.h"
|
||||
#include "clang/Basic/TargetInfo.h"
|
||||
#include "clang/Sema/Lookup.h"
|
||||
#include "toolchain/base/int.h"
|
||||
#include "toolchain/base/kind_switch.h"
|
||||
#include "toolchain/base/value_ids.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/check/convert.h"
|
||||
#include "toolchain/check/literal.h"
|
||||
#include "toolchain/sem_ir/class.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
#include "toolchain/sem_ir/inst.h"
|
||||
#include "toolchain/sem_ir/type.h"
|
||||
#include "toolchain/sem_ir/type_info.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Find the bit width of an integer literal.
|
||||
// The default bit width is 32. If the literal's bit width is greater than 32,
|
||||
// the bit width is increased to 64.
|
||||
static auto FindIntLiteralBitWidth(Context& context, SemIR::InstId arg_id)
|
||||
-> IntId {
|
||||
auto arg_const_id = context.constant_values().Get(arg_id);
|
||||
if (!arg_const_id.is_constant() ||
|
||||
arg_const_id == SemIR::ErrorInst::ConstantId ||
|
||||
arg_const_id.is_symbolic()) {
|
||||
// TODO: Add tests for these cases.
|
||||
return IntId::None;
|
||||
}
|
||||
auto arg = context.insts().GetAs<SemIR::IntValue>(
|
||||
context.constant_values().GetInstId(arg_const_id));
|
||||
unsigned arg_non_sign_bits =
|
||||
context.ints().Get(arg.int_id).getSignificantBits() - 1;
|
||||
|
||||
// TODO: What if the literal is larger than 64 bits? Currently an error is
|
||||
// reported that the int value is too large for type `i64`. Maybe try to fit
|
||||
// in i128/i256? Try unsigned?
|
||||
return (arg_non_sign_bits <= 32) ? IntId::MakeRaw(32) : IntId::MakeRaw(64);
|
||||
}
|
||||
|
||||
// Attempts to look up a type by name, and returns the corresponding `QualType`,
|
||||
// or a null type if lookup fails. `name_components` is the full path of the
|
||||
// type, including any namespaces or nested types, separated into separate
|
||||
// strings.
|
||||
static auto LookupCppType(
|
||||
Context& context, std::initializer_list<llvm::StringRef> name_components)
|
||||
-> clang::QualType {
|
||||
clang::ASTUnit* ast = context.sem_ir().clang_ast_unit();
|
||||
CARBON_CHECK(ast);
|
||||
clang::Sema& sema = ast->getSema();
|
||||
|
||||
clang::Decl* decl = sema.getASTContext().getTranslationUnitDecl();
|
||||
for (auto name_component : name_components) {
|
||||
auto* scope = dyn_cast<clang::DeclContext>(decl);
|
||||
if (!scope) {
|
||||
return clang::QualType();
|
||||
}
|
||||
|
||||
// TODO: Map the LocId of the lookup to a clang SourceLocation and provide
|
||||
// it here so that clang's diagnostics can point into the carbon code that
|
||||
// uses the name.
|
||||
auto* identifier = sema.getPreprocessor().getIdentifierInfo(name_component);
|
||||
clang::LookupResult lookup(
|
||||
sema, clang::DeclarationNameInfo(identifier, clang::SourceLocation()),
|
||||
clang::Sema::LookupNameKind::LookupOrdinaryName);
|
||||
if (!sema.LookupQualifiedName(lookup, scope) || !lookup.isSingleResult()) {
|
||||
return clang::QualType();
|
||||
}
|
||||
decl = lookup.getFoundDecl();
|
||||
}
|
||||
|
||||
auto* type_decl = dyn_cast<clang::TypeDecl>(decl);
|
||||
return type_decl ? sema.getASTContext().getTypeDeclType(type_decl)
|
||||
: clang::QualType();
|
||||
}
|
||||
|
||||
// Maps a Carbon class type to a C++ type. Returns a null `QualType` if the
|
||||
// type is not supported.
|
||||
static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
|
||||
-> clang::QualType {
|
||||
// If the class was imported from C++, return the original C++ type.
|
||||
auto clang_decl_id =
|
||||
context.name_scopes()
|
||||
.Get(context.sem_ir().classes().Get(class_type.class_id).scope_id)
|
||||
.clang_decl_context_id();
|
||||
if (clang_decl_id.has_value()) {
|
||||
clang::Decl* clang_decl =
|
||||
context.sem_ir().clang_decls().Get(clang_decl_id).decl;
|
||||
auto* tag_type_decl = clang::cast<clang::TagDecl>(clang_decl);
|
||||
return context.ast_context().getCanonicalTagType(tag_type_decl);
|
||||
}
|
||||
|
||||
// If the class represents a Carbon type literal, map it to the corresponding
|
||||
// C++ builtin type.
|
||||
auto literal = SemIR::TypeLiteralInfo::ForType(context.sem_ir(), class_type);
|
||||
switch (literal.kind) {
|
||||
case SemIR::TypeLiteralInfo::None: {
|
||||
break;
|
||||
}
|
||||
case SemIR::TypeLiteralInfo::Numeric: {
|
||||
switch (literal.numeric.kind) {
|
||||
case SemIR::NumericTypeLiteralInfo::None: {
|
||||
CARBON_FATAL("Unexpected invalid numeric type literal");
|
||||
}
|
||||
case SemIR::NumericTypeLiteralInfo::Float: {
|
||||
return context.ast_context().getRealTypeForBitwidth(
|
||||
literal.numeric.bit_width_id.AsValue(),
|
||||
clang::FloatModeKind::NoFloat);
|
||||
}
|
||||
case SemIR::NumericTypeLiteralInfo::Int: {
|
||||
return context.ast_context().getIntTypeForBitwidth(
|
||||
literal.numeric.bit_width_id.AsValue(), true);
|
||||
}
|
||||
case SemIR::NumericTypeLiteralInfo::UInt: {
|
||||
return context.ast_context().getIntTypeForBitwidth(
|
||||
literal.numeric.bit_width_id.AsValue(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
case SemIR::TypeLiteralInfo::Char: {
|
||||
return context.ast_context().CharTy;
|
||||
}
|
||||
case SemIR::TypeLiteralInfo::Str: {
|
||||
return LookupCppType(context, {"std", "string_view"});
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise we don't have a mapping for this Carbon class type.
|
||||
// TODO: If the class type wasn't imported from C++, create a corresponding
|
||||
// C++ class type.
|
||||
return clang::QualType();
|
||||
}
|
||||
|
||||
// Maps a non-wrapper (no const or pointer) Carbon type to a C++ type. Returns a
|
||||
// null QualType if the type is not supported.
|
||||
// TODO: Have both Carbon -> C++ and C++ -> Carbon mappings in a single place
|
||||
// to keep them in sync.
|
||||
static auto MapNonWrapperType(Context& context, SemIR::InstId inst_id,
|
||||
SemIR::TypeId type_id) -> clang::QualType {
|
||||
auto type_inst = context.sem_ir().types().GetAsInst(type_id);
|
||||
|
||||
CARBON_KIND_SWITCH(type_inst) {
|
||||
case SemIR::BoolType::Kind: {
|
||||
return context.ast_context().BoolTy;
|
||||
}
|
||||
case Carbon::SemIR::CharLiteralType::Kind: {
|
||||
return context.ast_context().CharTy;
|
||||
}
|
||||
case CARBON_KIND(SemIR::ClassType class_type): {
|
||||
return TryMapClassType(context, class_type);
|
||||
}
|
||||
case SemIR::IntLiteralType::Kind: {
|
||||
IntId bit_width_id = FindIntLiteralBitWidth(context, inst_id);
|
||||
if (bit_width_id == IntId::None) {
|
||||
return clang::QualType();
|
||||
}
|
||||
return context.ast_context().getIntTypeForBitwidth(bit_width_id.AsValue(),
|
||||
true);
|
||||
}
|
||||
// TODO: What if the value doesn't fit to f64?
|
||||
case SemIR::FloatLiteralType::Kind: {
|
||||
return context.ast_context().DoubleTy;
|
||||
}
|
||||
default: {
|
||||
return clang::QualType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: unify this with the C++ to Carbon type mapping function.
|
||||
auto MapToCppType(Context& context, SemIR::InstId inst_id) -> clang::QualType {
|
||||
auto type_id = context.insts().Get(inst_id).type_id();
|
||||
llvm::SmallVector<SemIR::TypeId> wrapper_types;
|
||||
while (true) {
|
||||
SemIR::TypeId orig_type_id = type_id;
|
||||
if (auto const_type =
|
||||
context.sem_ir().types().TryGetAs<SemIR::ConstType>(type_id);
|
||||
const_type) {
|
||||
type_id =
|
||||
context.sem_ir().types().GetTypeIdForTypeInstId(const_type->inner_id);
|
||||
} else if (auto pointer_type =
|
||||
context.sem_ir().types().TryGetAs<SemIR::PointerType>(
|
||||
type_id);
|
||||
pointer_type) {
|
||||
type_id = context.sem_ir().types().GetTypeIdForTypeInstId(
|
||||
pointer_type->pointee_id);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
wrapper_types.push_back(orig_type_id);
|
||||
}
|
||||
|
||||
clang::QualType mapped_type = MapNonWrapperType(context, inst_id, type_id);
|
||||
if (mapped_type.isNull()) {
|
||||
return mapped_type;
|
||||
}
|
||||
|
||||
for (auto wrapper_type_id : llvm::reverse(wrapper_types)) {
|
||||
if (auto const_type = context.sem_ir().types().TryGetAs<SemIR::ConstType>(
|
||||
wrapper_type_id);
|
||||
const_type) {
|
||||
mapped_type.addConst();
|
||||
} else if (context.sem_ir().types().TryGetAs<SemIR::PointerType>(
|
||||
wrapper_type_id)) {
|
||||
auto pointer_type = context.ast_context().getPointerType(mapped_type);
|
||||
mapped_type = context.ast_context().getAttributedType(
|
||||
clang::attr::TypeNonNull, pointer_type, pointer_type);
|
||||
} else {
|
||||
return clang::QualType();
|
||||
}
|
||||
}
|
||||
|
||||
return mapped_type;
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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
|
||||
|
||||
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_TYPE_MAPPING_H_
|
||||
#define CARBON_TOOLCHAIN_CHECK_CPP_TYPE_MAPPING_H_
|
||||
|
||||
#include "clang/AST/Type.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Maps a Carbon type to a C++ type. Accepts an InstId, representing a value
|
||||
// whose type is mapped to a C++ type. Returns `clang::QualType` if the mapping
|
||||
// succeeds, or `clang::QualType::isNull()` if the type is not supported.
|
||||
auto MapToCppType(Context& context, SemIR::InstId inst_id) -> clang::QualType;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
#endif // CARBON_TOOLCHAIN_CHECK_CPP_TYPE_MAPPING_H_
|
||||
Reference in New Issue
Block a user