From d37f1ae6b526d8601f571222e7676932c0335d98 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 25 Aug 2025 19:23:38 -0700 Subject: [PATCH] Add return value support to C++ thunks. (#5976) Based on #5948. A couple of tricky parts: * When generating the C++ side of the thunk, we are given a pointer to the location to emplace the return value. The only mechanism C++ provides to perform this emplacement is using placement `operator new`, which requires a library function in the `` header. We handle this by declaring that library function ourselves, and rely on Clang not actually needing a definition for it (which the standard library owns). * On the Carbon side of the thunk, we want to form an initializing expression as the result of the call. We don't have a way of expressing in SemIR that an initializing expression performs its initialization by storing through a pointer, so this PR adds a new initializing instruction, `InPlaceInit`, to model an initialization that's performed opaquely in-place. --- toolchain/check/cpp_thunk.cpp | 231 +++++++-- toolchain/check/import_cpp.cpp | 28 ++ .../function/arithmetic_types_bridged.carbon | 81 ++- .../interop/cpp/function/class.carbon | 37 +- .../interop/cpp/function/full_semir.carbon | 40 +- .../interop/cpp/function/return.carbon | 26 +- .../interop/cpp/function/struct.carbon | 38 +- .../interop/cpp/function/thunk_ast.carbon | 52 +- .../interop/cpp/function/union.carbon | 37 +- .../testdata/interop/cpp/namespace.carbon | 19 +- toolchain/lower/handle_expr_category.cpp | 34 ++ .../lower/testdata/interop/cpp/method.carbon | 15 +- .../cpp/{thunk.carbon => parameters.carbon} | 4 +- .../lower/testdata/interop/cpp/return.carbon | 465 ++++++++++++++++++ toolchain/sem_ir/expr_info.cpp | 7 + toolchain/sem_ir/inst_kind.def | 1 + toolchain/sem_ir/typed_insts.h | 20 + 17 files changed, 1039 insertions(+), 96 deletions(-) rename toolchain/lower/testdata/interop/cpp/{thunk.carbon => parameters.carbon} (99%) create mode 100644 toolchain/lower/testdata/interop/cpp/return.carbon diff --git a/toolchain/check/cpp_thunk.cpp b/toolchain/check/cpp_thunk.cpp index 98ff94254b53..6f123d0b0c67 100644 --- a/toolchain/check/cpp_thunk.cpp +++ b/toolchain/check/cpp_thunk.cpp @@ -74,34 +74,46 @@ auto IsCppThunkRequired(Context& context, const SemIR::Function& function) return false; } - SemIR::TypeId return_type_id = - function.GetDeclaredReturnType(context.sem_ir()); - if (return_type_id.has_value()) { - // TODO: Support non-void return values. + if (isa( + context.sem_ir().clang_decls().Get(function.clang_decl_id).decl)) { + // TODO: Support generating thunks for constructors. return false; } - bool thunk_required_for_param = 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; + + 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_for_param && + if (!thunk_required && IsThunkRequiredForType( context, context.insts().GetAs(param_id).type_id)) { - thunk_required_for_param = true; + thunk_required = true; } } - return thunk_required_for_param; + return thunk_required; } -// Returns whether the type is a pointer or a signed int of 32 or 64 bits. +// 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->isPointerType()) { + if (type->isVoidType() || type->isPointerType()) { return true; } @@ -125,6 +137,8 @@ struct CalleeFunctionInfo { if (has_object_parameter && method_decl->isImplicitObjectMemberFunction()) { implicit_this_type = method_decl->getThisType(); } + has_simple_return_type = + IsSimpleAbiType(decl->getASTContext(), decl->getReturnType()); } // Returns whether this callee has an implicit `this` parameter. @@ -139,13 +153,23 @@ struct CalleeFunctionInfo { // Returns the number of parameters the thunk should have. auto num_thunk_params() const -> unsigned { - return has_implicit_object_parameter() + decl->getNumParams(); + 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; @@ -156,9 +180,32 @@ struct CalleeFunctionInfo { // If the callee has an implicit object parameter, the corresponding `this` // type. Otherwise a null type. clang::QualType implicit_this_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)); +} + // Creates the thunk parameter types given the callee function. static auto BuildThunkParameterTypes(clang::ASTContext& ast_context, CalleeFunctionInfo callee_info) @@ -166,21 +213,22 @@ static auto BuildThunkParameterTypes(clang::ASTContext& ast_context, llvm::SmallVector thunk_param_types; thunk_param_types.reserve(callee_info.num_thunk_params()); if (callee_info.has_implicit_object_parameter()) { - thunk_param_types.push_back(ast_context.getAttributedType( - clang::NullabilityKind::NonNull, callee_info.implicit_this_type, - callee_info.implicit_this_type)); + thunk_param_types.push_back( + GetNonnullType(ast_context, callee_info.implicit_this_type)); } for (const clang::ParmVarDecl* callee_param : callee_info.decl->parameters()) { - clang::QualType param_type = callee_param->getType(); - bool is_simple_abi_type = IsSimpleAbiType(ast_context, param_type); - if (!is_simple_abi_type) { - clang::QualType pointer_type = ast_context.getPointerType(param_type); - param_type = ast_context.getAttributedType( - clang::NullabilityKind::NonNull, pointer_type, pointer_type); - } - thunk_param_types.push_back(param_type); + // 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.decl->getReturnType()))); } CARBON_CHECK(thunk_param_types.size() == callee_info.num_thunk_params()); @@ -220,6 +268,16 @@ static auto BuildThunkParameters(clang::ASTContext& ast_context, 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; } @@ -237,7 +295,9 @@ static auto CreateThunkFunctionDecl( auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo(); clang::QualType thunk_function_type = ast_context.getFunctionType( - callee_info.decl->getReturnType(), thunk_param_types, ext_proto_info); + callee_info.has_simple_return_type ? callee_info.decl->getReturnType() + : 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`. @@ -264,11 +324,13 @@ static auto CreateThunkFunctionDecl( return thunk_function_decl; } -// Builds a reference to the given parameter thunk, which is carrying a value of -// the given type. +// 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) + unsigned thunk_index, + clang::QualType type = clang::QualType()) -> clang::Expr* { clang::ParmVarDecl* thunk_param = thunk_function_decl->getParamDecl(thunk_index); @@ -277,7 +339,7 @@ static auto BuildThunkParamRef(clang::Sema& sema, clang::Expr* call_arg = sema.BuildDeclRefExpr( thunk_param, thunk_param->getType().getNonReferenceType(), clang::VK_LValue, clang_loc); - if (thunk_param->getType() != type) { + if (!type.isNull() && thunk_param->getType() != type) { // TODO: Consider inserting a cast to an rvalue. 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 @@ -329,6 +391,9 @@ 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 @@ -337,9 +402,9 @@ static auto BuildThunkBody(clang::Sema& sema, if (callee_info.has_object_parameter) { auto* object_param_ref = BuildThunkParamRef(sema, thunk_function_decl, 0, - callee_info.has_implicit_object_parameter() - ? thunk_function_decl->getParamDecl(0)->getType() - : callee_info.decl->getParamDecl(0)->getType()); + 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); @@ -373,9 +438,20 @@ static auto BuildThunkBody(clang::Sema& sema, return clang::StmtError(); } - // TODO: Consider building a CompoundStmt holding this to make our result more - // closely resemble a real C++ function. - return sema.BuildReturnStmt(clang_loc, call.get()); + 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.decl->getReturnType(); + 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) @@ -413,22 +489,55 @@ auto PerformCppThunkCall(Context& context, SemIR::LocId loc_id, SemIR::FunctionId callee_function_id, llvm::ArrayRef callee_arg_ids, SemIR::InstId thunk_callee_id) -> SemIR::InstId { - llvm::ArrayRef callee_function_params = - context.inst_blocks().GetOrEmpty( - context.functions().Get(callee_function_id).call_params_id); + auto& callee_function = context.functions().Get(callee_function_id); + auto callee_function_params = + context.inst_blocks().Get(callee_function.call_params_id); - llvm::ArrayRef thunk_function_params = - context.inst_blocks().GetOrEmpty( - context.functions() - .Get(GetCalleeFunction(context.sem_ir(), thunk_callee_id) - .function_id) - .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); - size_t num_params = callee_function_params.size(); - CARBON_CHECK(thunk_function_params.size() == num_params); - CARBON_CHECK(callee_arg_ids.size() == num_params); + // 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 thunk_arg_ids; - thunk_arg_ids.reserve(num_params); + 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)) { @@ -453,7 +562,35 @@ auto PerformCppThunkCall(Context& context, SemIR::LocId loc_id, thunk_arg_ids.push_back(arg_id); } - return PerformCall(context, loc_id, thunk_callee_id, thunk_arg_ids); + // 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 = AddInstWithCleanup( + context, loc_id, {.type_id = return_type_id}); + } + + auto arg_id = AddInst( + 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(context, loc_id, + {.type_id = return_type_id, + .src_id = result_id, + .dest_id = return_slot_id}); + } + + return result_id; } } // namespace Carbon::Check diff --git a/toolchain/check/import_cpp.cpp b/toolchain/check/import_cpp.cpp index 4aacddafa71c..281491da31d0 100644 --- a/toolchain/check/import_cpp.cpp +++ b/toolchain/check/import_cpp.cpp @@ -102,6 +102,34 @@ static auto GenerateCppIncludesHeaderCode( << "\"\n"; } } + + // Inject a declaration of placement operator new, because the code we + // generate in thunks depends on it for placement new expressions. Clang has + // special-case logic for lowering a new-expression using this, so a + // definition is not required. + // TODO: This is a hack. We should be able to directly generate Clang AST to + // construct objects in-place without this. + // TODO: Once we can rely on libc++ being available, consider including + // `<__new/placement_new_delete.h>` instead. + code_stream << R"(# 1 "" +#undef constexpr +#if __cplusplus > 202302L +constexpr +#endif +#undef void +#undef operator +#undef new +void* operator new(__SIZE_TYPE__, void*) +#if __cplusplus < 201103L +#undef throw +throw() +#else +#undef noexcept +noexcept +#endif +; +)"; + return code; } diff --git a/toolchain/check/testdata/interop/cpp/function/arithmetic_types_bridged.carbon b/toolchain/check/testdata/interop/cpp/function/arithmetic_types_bridged.carbon index 397f373e8d27..4b20cb855869 100644 --- a/toolchain/check/testdata/interop/cpp/function/arithmetic_types_bridged.carbon +++ b/toolchain/check/testdata/interop/cpp/function/arithmetic_types_bridged.carbon @@ -1642,11 +1642,17 @@ fn F() { // CHECK:STDOUT: --- import_bool_return.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] // CHECK:STDOUT: %Bool.type: type = fn_type @Bool [concrete] // CHECK:STDOUT: %Bool: %Bool.type = struct_value () [concrete] // CHECK:STDOUT: %pattern_type.831: type = pattern_type bool [concrete] // CHECK:STDOUT: %foo_bool.type: type = fn_type @foo_bool [concrete] // CHECK:STDOUT: %foo_bool: %foo_bool.type = struct_value () [concrete] +// CHECK:STDOUT: %ptr.bb2: type = ptr_type bool [concrete] +// CHECK:STDOUT: %foo_bool__carbon_thunk.type: type = fn_type @foo_bool__carbon_thunk [concrete] +// CHECK:STDOUT: %foo_bool__carbon_thunk: %foo_bool__carbon_thunk.type = struct_value () [concrete] +// CHECK:STDOUT: %T.as.Destroy.impl.Op.type.655: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(bool) [concrete] +// CHECK:STDOUT: %T.as.Destroy.impl.Op.8b7: %T.as.Destroy.impl.Op.type.655 = struct_value () [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -1659,6 +1665,11 @@ fn F() { // CHECK:STDOUT: } { // CHECK:STDOUT: // CHECK:STDOUT: } +// CHECK:STDOUT: %foo_bool__carbon_thunk.decl: %foo_bool__carbon_thunk.type = fn_decl @foo_bool__carbon_thunk [concrete = constants.%foo_bool__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F() { @@ -1668,26 +1679,40 @@ fn F() { // CHECK:STDOUT: } // CHECK:STDOUT: %Cpp.ref: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %foo_bool.ref: %foo_bool.type = name_ref foo_bool, imports.%foo_bool.decl [concrete = constants.%foo_bool] -// CHECK:STDOUT: %foo_bool.call: init bool = call %foo_bool.ref() +// CHECK:STDOUT: %.loc8_30.1: ref bool = temporary_storage +// CHECK:STDOUT: %addr.loc8_30.1: %ptr.bb2 = addr_of %.loc8_30.1 +// CHECK:STDOUT: %foo_bool__carbon_thunk.call: init %empty_tuple.type = call imports.%foo_bool__carbon_thunk.decl(%addr.loc8_30.1) +// CHECK:STDOUT: %.loc8_30.2: init bool = in_place_init %foo_bool__carbon_thunk.call, %.loc8_30.1 // CHECK:STDOUT: %.loc8_10.1: type = splice_block %.loc8_10.3 [concrete = bool] { // CHECK:STDOUT: %Bool.call: init type = call constants.%Bool() [concrete = bool] // CHECK:STDOUT: %.loc8_10.2: type = value_of_initializer %Bool.call [concrete = bool] // CHECK:STDOUT: %.loc8_10.3: type = converted %Bool.call, %.loc8_10.2 [concrete = bool] // CHECK:STDOUT: } -// CHECK:STDOUT: %.loc8_30.1: bool = value_of_initializer %foo_bool.call -// CHECK:STDOUT: %.loc8_30.2: bool = converted %foo_bool.call, %.loc8_30.1 -// CHECK:STDOUT: %x: bool = bind_name x, %.loc8_30.2 +// CHECK:STDOUT: %.loc8_30.3: bool = value_of_initializer %.loc8_30.2 +// CHECK:STDOUT: %.loc8_30.4: bool = converted %.loc8_30.2, %.loc8_30.3 +// CHECK:STDOUT: %x: bool = bind_name x, %.loc8_30.4 +// CHECK:STDOUT: %T.as.Destroy.impl.Op.bound: = bound_method %.loc8_30.1, constants.%T.as.Destroy.impl.Op.8b7 +// CHECK:STDOUT: +// CHECK:STDOUT: %bound_method: = bound_method %.loc8_30.1, %T.as.Destroy.impl.Op.specific_fn +// CHECK:STDOUT: %addr.loc8_30.2: %ptr.bb2 = addr_of %.loc8_30.1 +// CHECK:STDOUT: %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc8_30.2) // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: --- import_short_return.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] // CHECK:STDOUT: %int_16: Core.IntLiteral = int_value 16 [concrete] // CHECK:STDOUT: %i16: type = class_type @Int, @Int(%int_16) [concrete] // CHECK:STDOUT: %pattern_type.2f8: type = pattern_type %i16 [concrete] // CHECK:STDOUT: %foo_short.type: type = fn_type @foo_short [concrete] // CHECK:STDOUT: %foo_short: %foo_short.type = struct_value () [concrete] +// CHECK:STDOUT: %ptr.251: type = ptr_type %i16 [concrete] +// CHECK:STDOUT: %foo_short__carbon_thunk.type: type = fn_type @foo_short__carbon_thunk [concrete] +// CHECK:STDOUT: %foo_short__carbon_thunk: %foo_short__carbon_thunk.type = struct_value () [concrete] +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.type.613: type = fn_type @Int.as.Destroy.impl.Op, @Int.as.Destroy.impl(%int_16) [concrete] +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.536: %Int.as.Destroy.impl.Op.type.613 = struct_value () [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -1700,6 +1725,11 @@ fn F() { // CHECK:STDOUT: } { // CHECK:STDOUT: // CHECK:STDOUT: } +// CHECK:STDOUT: %foo_short__carbon_thunk.decl: %foo_short__carbon_thunk.type = fn_decl @foo_short__carbon_thunk [concrete = constants.%foo_short__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F() { @@ -1709,14 +1739,22 @@ fn F() { // CHECK:STDOUT: } // CHECK:STDOUT: %Cpp.ref: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %foo_short.ref: %foo_short.type = name_ref foo_short, imports.%foo_short.decl [concrete = constants.%foo_short] -// CHECK:STDOUT: %foo_short.call: init %i16 = call %foo_short.ref() +// CHECK:STDOUT: %.loc8_30.1: ref %i16 = temporary_storage +// CHECK:STDOUT: %addr.loc8_30.1: %ptr.251 = addr_of %.loc8_30.1 +// CHECK:STDOUT: %foo_short__carbon_thunk.call: init %empty_tuple.type = call imports.%foo_short__carbon_thunk.decl(%addr.loc8_30.1) +// CHECK:STDOUT: %.loc8_30.2: init %i16 = in_place_init %foo_short__carbon_thunk.call, %.loc8_30.1 // CHECK:STDOUT: %.loc8_10: type = splice_block %i16 [concrete = constants.%i16] { // CHECK:STDOUT: %int_16: Core.IntLiteral = int_value 16 [concrete = constants.%int_16] // CHECK:STDOUT: %i16: type = class_type @Int, @Int(constants.%int_16) [concrete = constants.%i16] // CHECK:STDOUT: } -// CHECK:STDOUT: %.loc8_30.1: %i16 = value_of_initializer %foo_short.call -// CHECK:STDOUT: %.loc8_30.2: %i16 = converted %foo_short.call, %.loc8_30.1 -// CHECK:STDOUT: %x: %i16 = bind_name x, %.loc8_30.2 +// CHECK:STDOUT: %.loc8_30.3: %i16 = value_of_initializer %.loc8_30.2 +// CHECK:STDOUT: %.loc8_30.4: %i16 = converted %.loc8_30.2, %.loc8_30.3 +// CHECK:STDOUT: %x: %i16 = bind_name x, %.loc8_30.4 +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.bound: = bound_method %.loc8_30.1, constants.%Int.as.Destroy.impl.Op.536 +// CHECK:STDOUT: +// CHECK:STDOUT: %bound_method: = bound_method %.loc8_30.1, %Int.as.Destroy.impl.Op.specific_fn +// CHECK:STDOUT: %addr.loc8_30.2: %ptr.251 = addr_of %.loc8_30.1 +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc8_30.2) // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: @@ -1744,11 +1782,17 @@ fn F() { // CHECK:STDOUT: --- import_double_return.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] // CHECK:STDOUT: %int_64: Core.IntLiteral = int_value 64 [concrete] // CHECK:STDOUT: %f64.d77: type = class_type @Float, @Float(%int_64) [concrete] // CHECK:STDOUT: %pattern_type.0ae: type = pattern_type %f64.d77 [concrete] // CHECK:STDOUT: %foo_double.type: type = fn_type @foo_double [concrete] // CHECK:STDOUT: %foo_double: %foo_double.type = struct_value () [concrete] +// CHECK:STDOUT: %ptr.bcc: type = ptr_type %f64.d77 [concrete] +// CHECK:STDOUT: %foo_double__carbon_thunk.type: type = fn_type @foo_double__carbon_thunk [concrete] +// CHECK:STDOUT: %foo_double__carbon_thunk: %foo_double__carbon_thunk.type = struct_value () [concrete] +// CHECK:STDOUT: %Float.as.Destroy.impl.Op.type.cd5: type = fn_type @Float.as.Destroy.impl.Op, @Float.as.Destroy.impl(%int_64) [concrete] +// CHECK:STDOUT: %Float.as.Destroy.impl.Op.b8c: %Float.as.Destroy.impl.Op.type.cd5 = struct_value () [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -1761,6 +1805,11 @@ fn F() { // CHECK:STDOUT: } { // CHECK:STDOUT: // CHECK:STDOUT: } +// CHECK:STDOUT: %foo_double__carbon_thunk.decl: %foo_double__carbon_thunk.type = fn_decl @foo_double__carbon_thunk [concrete = constants.%foo_double__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F() { @@ -1770,14 +1819,22 @@ fn F() { // CHECK:STDOUT: } // CHECK:STDOUT: %Cpp.ref: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %foo_double.ref: %foo_double.type = name_ref foo_double, imports.%foo_double.decl [concrete = constants.%foo_double] -// CHECK:STDOUT: %foo_double.call: init %f64.d77 = call %foo_double.ref() +// CHECK:STDOUT: %.loc8_31.1: ref %f64.d77 = temporary_storage +// CHECK:STDOUT: %addr.loc8_31.1: %ptr.bcc = addr_of %.loc8_31.1 +// CHECK:STDOUT: %foo_double__carbon_thunk.call: init %empty_tuple.type = call imports.%foo_double__carbon_thunk.decl(%addr.loc8_31.1) +// CHECK:STDOUT: %.loc8_31.2: init %f64.d77 = in_place_init %foo_double__carbon_thunk.call, %.loc8_31.1 // CHECK:STDOUT: %.loc8_10: type = splice_block %f64 [concrete = constants.%f64.d77] { // CHECK:STDOUT: %int_64: Core.IntLiteral = int_value 64 [concrete = constants.%int_64] // CHECK:STDOUT: %f64: type = class_type @Float, @Float(constants.%int_64) [concrete = constants.%f64.d77] // CHECK:STDOUT: } -// CHECK:STDOUT: %.loc8_31.1: %f64.d77 = value_of_initializer %foo_double.call -// CHECK:STDOUT: %.loc8_31.2: %f64.d77 = converted %foo_double.call, %.loc8_31.1 -// CHECK:STDOUT: %x: %f64.d77 = bind_name x, %.loc8_31.2 +// CHECK:STDOUT: %.loc8_31.3: %f64.d77 = value_of_initializer %.loc8_31.2 +// CHECK:STDOUT: %.loc8_31.4: %f64.d77 = converted %.loc8_31.2, %.loc8_31.3 +// CHECK:STDOUT: %x: %f64.d77 = bind_name x, %.loc8_31.4 +// CHECK:STDOUT: %Float.as.Destroy.impl.Op.bound: = bound_method %.loc8_31.1, constants.%Float.as.Destroy.impl.Op.b8c +// CHECK:STDOUT: +// CHECK:STDOUT: %bound_method: = bound_method %.loc8_31.1, %Float.as.Destroy.impl.Op.specific_fn +// CHECK:STDOUT: %addr.loc8_31.2: %ptr.bcc = addr_of %.loc8_31.1 +// CHECK:STDOUT: %Float.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc8_31.2) // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/interop/cpp/function/class.carbon b/toolchain/check/testdata/interop/cpp/function/class.carbon index b14bebaf7513..4bb0100e12a1 100644 --- a/toolchain/check/testdata/interop/cpp/function/class.carbon +++ b/toolchain/check/testdata/interop/cpp/function/class.carbon @@ -447,13 +447,29 @@ auto foo() -> C; library "[[@TEST_NAME]]"; +// CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+12]]:10: in file included here [InCppInclude] +// CHECK:STDERR: ./decl_value_return_type.h:4:6: error: calling 'foo' with incomplete return type 'C' [CppInteropParseError] +// CHECK:STDERR: 4 | auto foo() -> C; +// CHECK:STDERR: | ^~~ +// CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+8]]:10: in file included here [InCppInclude] +// CHECK:STDERR: ./decl_value_return_type.h:4:6: note: 'foo' declared here [CppInteropParseNote] +// CHECK:STDERR: 4 | auto foo() -> C; +// CHECK:STDERR: | ^ +// CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+4]]:10: in file included here [InCppInclude] +// CHECK:STDERR: ./decl_value_return_type.h:2:7: note: forward declaration of 'C' [CppInteropParseNote] +// CHECK:STDERR: 2 | class C; +// CHECK:STDERR: | ^ import Cpp library "decl_value_return_type.h"; fn F() { + // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+13]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup] + // CHECK:STDERR: Cpp.foo(); + // CHECK:STDERR: ^~~~~~~ + // CHECK:STDERR: // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+9]]:3: error: function returns incomplete type `Cpp.C` [IncompleteTypeInFunctionReturnType] // CHECK:STDERR: Cpp.foo(); // CHECK:STDERR: ^~~~~~~~~ - // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE-6]]:10: in file included here [InCppInclude] + // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE-10]]:10: in file included here [InCppInclude] // CHECK:STDERR: ./decl_value_return_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere] // CHECK:STDERR: class C; // CHECK:STDERR: ^ @@ -1157,9 +1173,11 @@ fn F() { // CHECK:STDOUT: %C: type = class_type @C [concrete] // CHECK:STDOUT: %foo.type: type = fn_type @foo [concrete] // CHECK:STDOUT: %foo: %foo.type = struct_value () [concrete] +// CHECK:STDOUT: %ptr.d9e: type = ptr_type %C [concrete] +// CHECK:STDOUT: %foo__carbon_thunk.type: type = fn_type @foo__carbon_thunk [concrete] +// CHECK:STDOUT: %foo__carbon_thunk: %foo__carbon_thunk.type = struct_value () [concrete] // CHECK:STDOUT: %T.as.Destroy.impl.Op.type.1b3: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%C) [concrete] // CHECK:STDOUT: %T.as.Destroy.impl.Op.21b: %T.as.Destroy.impl.Op.type.1b3 = struct_value () [concrete] -// CHECK:STDOUT: %ptr.d9e: type = ptr_type %C [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -1172,6 +1190,11 @@ fn F() { // CHECK:STDOUT: } { // CHECK:STDOUT: // CHECK:STDOUT: } +// CHECK:STDOUT: %foo__carbon_thunk.decl: %foo__carbon_thunk.type = fn_decl @foo__carbon_thunk [concrete = constants.%foo__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F() { @@ -1179,13 +1202,15 @@ fn F() { // CHECK:STDOUT: %Cpp.ref: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo] // CHECK:STDOUT: %.loc8_11.1: ref %C = temporary_storage -// CHECK:STDOUT: %foo.call: init %C = call %foo.ref() to %.loc8_11.1 -// CHECK:STDOUT: %.loc8_11.2: ref %C = temporary %.loc8_11.1, %foo.call +// CHECK:STDOUT: %addr.loc8_11.1: %ptr.d9e = addr_of %.loc8_11.1 +// CHECK:STDOUT: %foo__carbon_thunk.call: init %empty_tuple.type = call imports.%foo__carbon_thunk.decl(%addr.loc8_11.1) +// CHECK:STDOUT: %.loc8_11.2: init %C = in_place_init %foo__carbon_thunk.call, %.loc8_11.1 +// CHECK:STDOUT: %.loc8_11.3: ref %C = temporary %.loc8_11.1, %.loc8_11.2 // CHECK:STDOUT: %T.as.Destroy.impl.Op.bound: = bound_method %.loc8_11.1, constants.%T.as.Destroy.impl.Op.21b // CHECK:STDOUT: // CHECK:STDOUT: %bound_method: = bound_method %.loc8_11.1, %T.as.Destroy.impl.Op.specific_fn -// CHECK:STDOUT: %addr: %ptr.d9e = addr_of %.loc8_11.1 -// CHECK:STDOUT: %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr) +// CHECK:STDOUT: %addr.loc8_11.2: %ptr.d9e = addr_of %.loc8_11.1 +// CHECK:STDOUT: %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc8_11.2) // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/interop/cpp/function/full_semir.carbon b/toolchain/check/testdata/interop/cpp/function/full_semir.carbon index 17f408f4909f..76d735636f83 100644 --- a/toolchain/check/testdata/interop/cpp/function/full_semir.carbon +++ b/toolchain/check/testdata/interop/cpp/function/full_semir.carbon @@ -284,6 +284,7 @@ fn F() { // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] // CHECK:STDOUT: %F: %F.type = struct_value () [concrete] // CHECK:STDOUT: %int_16: Core.IntLiteral = int_value 16 [concrete] // CHECK:STDOUT: %Int.type: type = generic_class_type @Int [concrete] @@ -292,11 +293,20 @@ fn F() { // CHECK:STDOUT: %pattern_type.2f8: type = pattern_type %i16 [concrete] // CHECK:STDOUT: %foo_short.type: type = fn_type @foo_short [concrete] // CHECK:STDOUT: %foo_short: %foo_short.type = struct_value () [concrete] +// CHECK:STDOUT: %ptr.251: type = ptr_type %i16 [concrete] +// CHECK:STDOUT: %pattern_type.54c: type = pattern_type %ptr.251 [concrete] +// CHECK:STDOUT: %foo_short__carbon_thunk.type: type = fn_type @foo_short__carbon_thunk [concrete] +// CHECK:STDOUT: %foo_short__carbon_thunk: %foo_short__carbon_thunk.type = struct_value () [concrete] +// CHECK:STDOUT: %Destroy.type: type = facet_type <@Destroy> [concrete] +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.type.613: type = fn_type @Int.as.Destroy.impl.Op, @Int.as.Destroy.impl(%int_16) [concrete] +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.536: %Int.as.Destroy.impl.Op.type.613 = struct_value () [concrete] +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.specific_fn: = specific_function %Int.as.Destroy.impl.Op.536, @Int.as.Destroy.impl.Op(%int_16) [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { // CHECK:STDOUT: %Core: = namespace file.%Core.import, [concrete] { // CHECK:STDOUT: .Int = %Core.Int +// CHECK:STDOUT: .Destroy = %Core.Destroy // CHECK:STDOUT: import Core//prelude // CHECK:STDOUT: import Core//prelude/... // CHECK:STDOUT: } @@ -314,6 +324,18 @@ fn F() { // CHECK:STDOUT: %return.param: ref %i16 = out_param call_param0 // CHECK:STDOUT: %return: ref %i16 = return_slot %return.param // CHECK:STDOUT: } +// CHECK:STDOUT: %foo_short__carbon_thunk.decl: %foo_short__carbon_thunk.type = fn_decl @foo_short__carbon_thunk [concrete = constants.%foo_short__carbon_thunk] { +// CHECK:STDOUT: %return.patt: %pattern_type.54c = binding_pattern r#return [concrete] +// CHECK:STDOUT: %return.param_patt: %pattern_type.54c = value_param_pattern %return.patt, call_param0 [concrete] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %return.param: %ptr.251 = value_param call_param0 +// CHECK:STDOUT: %.1: type = splice_block constants.%ptr.251 [concrete = constants.%ptr.251] { +// CHECK:STDOUT: %int_16: Core.IntLiteral = int_value 16 [concrete = constants.%int_16] +// CHECK:STDOUT: %i16: type = class_type @Int, @Int(constants.%int_16) [concrete = constants.%i16] +// CHECK:STDOUT: } +// CHECK:STDOUT: %return: %ptr.251 = bind_name r#return, %return.param +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.Destroy: type = import_ref Core//prelude/parts/destroy, Destroy, loaded [concrete = constants.%Destroy.type] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: file { @@ -336,16 +358,26 @@ fn F() { // CHECK:STDOUT: } // CHECK:STDOUT: %Cpp.ref: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %foo_short.ref: %foo_short.type = name_ref foo_short, imports.%foo_short.decl [concrete = constants.%foo_short] -// CHECK:STDOUT: %foo_short.call: init %i16 = call %foo_short.ref() +// CHECK:STDOUT: %.loc7_30.1: ref %i16 = temporary_storage +// CHECK:STDOUT: %addr.loc7_30.1: %ptr.251 = addr_of %.loc7_30.1 +// CHECK:STDOUT: %foo_short__carbon_thunk.call: init %empty_tuple.type = call imports.%foo_short__carbon_thunk.decl(%addr.loc7_30.1) +// CHECK:STDOUT: %.loc7_30.2: init %i16 = in_place_init %foo_short__carbon_thunk.call, %.loc7_30.1 // CHECK:STDOUT: %.loc7_10: type = splice_block %i16 [concrete = constants.%i16] { // CHECK:STDOUT: %int_16: Core.IntLiteral = int_value 16 [concrete = constants.%int_16] // CHECK:STDOUT: %i16: type = class_type @Int, @Int(constants.%int_16) [concrete = constants.%i16] // CHECK:STDOUT: } -// CHECK:STDOUT: %.loc7_30.1: %i16 = value_of_initializer %foo_short.call -// CHECK:STDOUT: %.loc7_30.2: %i16 = converted %foo_short.call, %.loc7_30.1 -// CHECK:STDOUT: %x: %i16 = bind_name x, %.loc7_30.2 +// CHECK:STDOUT: %.loc7_30.3: %i16 = value_of_initializer %.loc7_30.2 +// CHECK:STDOUT: %.loc7_30.4: %i16 = converted %.loc7_30.2, %.loc7_30.3 +// CHECK:STDOUT: %x: %i16 = bind_name x, %.loc7_30.4 +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.bound: = bound_method %.loc7_30.1, constants.%Int.as.Destroy.impl.Op.536 +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.specific_fn: = specific_function constants.%Int.as.Destroy.impl.Op.536, @Int.as.Destroy.impl.Op(constants.%int_16) [concrete = constants.%Int.as.Destroy.impl.Op.specific_fn] +// CHECK:STDOUT: %bound_method: = bound_method %.loc7_30.1, %Int.as.Destroy.impl.Op.specific_fn +// CHECK:STDOUT: %addr.loc7_30.2: %ptr.251 = addr_of %.loc7_30.1 +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc7_30.2) // CHECK:STDOUT: return // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @foo_short() -> %i16; // CHECK:STDOUT: +// CHECK:STDOUT: fn @foo_short__carbon_thunk(%return.param: %ptr.251); +// CHECK:STDOUT: diff --git a/toolchain/check/testdata/interop/cpp/function/return.carbon b/toolchain/check/testdata/interop/cpp/function/return.carbon index 0e4869f8aa1a..ced88dfec4f6 100644 --- a/toolchain/check/testdata/interop/cpp/function/return.carbon +++ b/toolchain/check/testdata/interop/cpp/function/return.carbon @@ -49,8 +49,13 @@ fn F() { // CHECK:STDOUT: %IngestI32: %IngestI32.type = struct_value () [concrete] // CHECK:STDOUT: %foo1.type: type = fn_type @foo1 [concrete] // CHECK:STDOUT: %foo1: %foo1.type = struct_value () [concrete] +// CHECK:STDOUT: %ptr.251: type = ptr_type %i16 [concrete] +// CHECK:STDOUT: %foo1__carbon_thunk.type: type = fn_type @foo1__carbon_thunk [concrete] +// CHECK:STDOUT: %foo1__carbon_thunk: %foo1__carbon_thunk.type = struct_value () [concrete] // CHECK:STDOUT: %foo2.type: type = fn_type @foo2 [concrete] // CHECK:STDOUT: %foo2: %foo2.type = struct_value () [concrete] +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.type.613: type = fn_type @Int.as.Destroy.impl.Op, @Int.as.Destroy.impl(%int_16) [concrete] +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.536: %Int.as.Destroy.impl.Op.type.613 = struct_value () [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -64,6 +69,11 @@ fn F() { // CHECK:STDOUT: } { // CHECK:STDOUT: // CHECK:STDOUT: } +// CHECK:STDOUT: %foo1__carbon_thunk.decl: %foo1__carbon_thunk.type = fn_decl @foo1__carbon_thunk [concrete = constants.%foo1__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } // CHECK:STDOUT: %foo2.decl: %foo2.type = fn_decl @foo2 [concrete = constants.%foo2] { // CHECK:STDOUT: // CHECK:STDOUT: } { @@ -76,10 +86,13 @@ fn F() { // CHECK:STDOUT: %IngestI16.ref: %IngestI16.type = name_ref IngestI16, file.%IngestI16.decl [concrete = constants.%IngestI16] // CHECK:STDOUT: %Cpp.ref.loc11: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %foo1.ref: %foo1.type = name_ref foo1, imports.%foo1.decl [concrete = constants.%foo1] -// CHECK:STDOUT: %foo1.call: init %i16 = call %foo1.ref() -// CHECK:STDOUT: %.loc11_22.1: %i16 = value_of_initializer %foo1.call -// CHECK:STDOUT: %.loc11_22.2: %i16 = converted %foo1.call, %.loc11_22.1 -// CHECK:STDOUT: %IngestI16.call: init %empty_tuple.type = call %IngestI16.ref(%.loc11_22.2) +// CHECK:STDOUT: %.loc11_22.1: ref %i16 = temporary_storage +// CHECK:STDOUT: %addr.loc11_22.1: %ptr.251 = addr_of %.loc11_22.1 +// CHECK:STDOUT: %foo1__carbon_thunk.call: init %empty_tuple.type = call imports.%foo1__carbon_thunk.decl(%addr.loc11_22.1) +// CHECK:STDOUT: %.loc11_22.2: init %i16 = in_place_init %foo1__carbon_thunk.call, %.loc11_22.1 +// CHECK:STDOUT: %.loc11_22.3: %i16 = value_of_initializer %.loc11_22.2 +// CHECK:STDOUT: %.loc11_22.4: %i16 = converted %.loc11_22.2, %.loc11_22.3 +// CHECK:STDOUT: %IngestI16.call: init %empty_tuple.type = call %IngestI16.ref(%.loc11_22.4) // CHECK:STDOUT: %IngestI32.ref: %IngestI32.type = name_ref IngestI32, file.%IngestI32.decl [concrete = constants.%IngestI32] // CHECK:STDOUT: %Cpp.ref.loc12: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %foo2.ref: %foo2.type = name_ref foo2, imports.%foo2.decl [concrete = constants.%foo2] @@ -87,6 +100,11 @@ fn F() { // CHECK:STDOUT: %.loc12_22.1: %i32 = value_of_initializer %foo2.call // CHECK:STDOUT: %.loc12_22.2: %i32 = converted %foo2.call, %.loc12_22.1 // CHECK:STDOUT: %IngestI32.call: init %empty_tuple.type = call %IngestI32.ref(%.loc12_22.2) +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.bound: = bound_method %.loc11_22.1, constants.%Int.as.Destroy.impl.Op.536 +// CHECK:STDOUT: +// CHECK:STDOUT: %bound_method: = bound_method %.loc11_22.1, %Int.as.Destroy.impl.Op.specific_fn +// CHECK:STDOUT: %addr.loc11_22.2: %ptr.251 = addr_of %.loc11_22.1 +// CHECK:STDOUT: %Int.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc11_22.2) // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/interop/cpp/function/struct.carbon b/toolchain/check/testdata/interop/cpp/function/struct.carbon index f8982bfc9995..8b25322758ef 100644 --- a/toolchain/check/testdata/interop/cpp/function/struct.carbon +++ b/toolchain/check/testdata/interop/cpp/function/struct.carbon @@ -445,13 +445,30 @@ auto foo() -> S; library "[[@TEST_NAME]]"; +// TODO: We should only produce one error here. +// CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+12]]:10: in file included here [InCppInclude] +// CHECK:STDERR: ./decl_value_return_type.h:4:6: error: calling 'foo' with incomplete return type 'S' [CppInteropParseError] +// CHECK:STDERR: 4 | auto foo() -> S; +// CHECK:STDERR: | ^~~ +// CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+8]]:10: in file included here [InCppInclude] +// CHECK:STDERR: ./decl_value_return_type.h:4:6: note: 'foo' declared here [CppInteropParseNote] +// CHECK:STDERR: 4 | auto foo() -> S; +// CHECK:STDERR: | ^ +// CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+4]]:10: in file included here [InCppInclude] +// CHECK:STDERR: ./decl_value_return_type.h:2:8: note: forward declaration of 'S' [CppInteropParseNote] +// CHECK:STDERR: 2 | struct S; +// CHECK:STDERR: | ^ import Cpp library "decl_value_return_type.h"; fn F() { + // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+13]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup] + // CHECK:STDERR: Cpp.foo(); + // CHECK:STDERR: ^~~~~~~ + // CHECK:STDERR: // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+9]]:3: error: function returns incomplete type `Cpp.S` [IncompleteTypeInFunctionReturnType] // CHECK:STDERR: Cpp.foo(); // CHECK:STDERR: ^~~~~~~~~ - // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE-6]]:10: in file included here [InCppInclude] + // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE-10]]:10: in file included here [InCppInclude] // CHECK:STDERR: ./decl_value_return_type.h:2:8: note: class was forward declared here [ClassForwardDeclaredHere] // CHECK:STDERR: struct S; // CHECK:STDERR: ^ @@ -1155,9 +1172,11 @@ fn F() { // CHECK:STDOUT: %S: type = class_type @S [concrete] // CHECK:STDOUT: %foo.type: type = fn_type @foo [concrete] // CHECK:STDOUT: %foo: %foo.type = struct_value () [concrete] +// CHECK:STDOUT: %ptr.5c7: type = ptr_type %S [concrete] +// CHECK:STDOUT: %foo__carbon_thunk.type: type = fn_type @foo__carbon_thunk [concrete] +// CHECK:STDOUT: %foo__carbon_thunk: %foo__carbon_thunk.type = struct_value () [concrete] // CHECK:STDOUT: %T.as.Destroy.impl.Op.type.642: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%S) [concrete] // CHECK:STDOUT: %T.as.Destroy.impl.Op.ab5: %T.as.Destroy.impl.Op.type.642 = struct_value () [concrete] -// CHECK:STDOUT: %ptr.5c7: type = ptr_type %S [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -1170,6 +1189,11 @@ fn F() { // CHECK:STDOUT: } { // CHECK:STDOUT: // CHECK:STDOUT: } +// CHECK:STDOUT: %foo__carbon_thunk.decl: %foo__carbon_thunk.type = fn_decl @foo__carbon_thunk [concrete = constants.%foo__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F() { @@ -1177,13 +1201,15 @@ fn F() { // CHECK:STDOUT: %Cpp.ref: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo] // CHECK:STDOUT: %.loc8_11.1: ref %S = temporary_storage -// CHECK:STDOUT: %foo.call: init %S = call %foo.ref() to %.loc8_11.1 -// CHECK:STDOUT: %.loc8_11.2: ref %S = temporary %.loc8_11.1, %foo.call +// CHECK:STDOUT: %addr.loc8_11.1: %ptr.5c7 = addr_of %.loc8_11.1 +// CHECK:STDOUT: %foo__carbon_thunk.call: init %empty_tuple.type = call imports.%foo__carbon_thunk.decl(%addr.loc8_11.1) +// CHECK:STDOUT: %.loc8_11.2: init %S = in_place_init %foo__carbon_thunk.call, %.loc8_11.1 +// CHECK:STDOUT: %.loc8_11.3: ref %S = temporary %.loc8_11.1, %.loc8_11.2 // CHECK:STDOUT: %T.as.Destroy.impl.Op.bound: = bound_method %.loc8_11.1, constants.%T.as.Destroy.impl.Op.ab5 // CHECK:STDOUT: // CHECK:STDOUT: %bound_method: = bound_method %.loc8_11.1, %T.as.Destroy.impl.Op.specific_fn -// CHECK:STDOUT: %addr: %ptr.5c7 = addr_of %.loc8_11.1 -// CHECK:STDOUT: %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr) +// CHECK:STDOUT: %addr.loc8_11.2: %ptr.5c7 = addr_of %.loc8_11.1 +// CHECK:STDOUT: %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc8_11.2) // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon b/toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon index f0b15bbea472..4f3b57e444b9 100644 --- a/toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon +++ b/toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon -// EXTRA-ARGS: --dump-cpp-ast +// EXTRA-ARGS: --dump-cpp-ast --target=x86_64-linux-gnu // SET-CHECK-SUBSET // // AUTOUPDATE @@ -18,7 +18,55 @@ auto foo(short a) -> void; // CHECK:STDOUT: |-FunctionDecl {{0x[a-f0-9]+}} <./thunk_required.h:[[@LINE-1]]:1, col:22> col:6 used foo 'auto (short) -> void' // CHECK:STDOUT: | `-ParmVarDecl {{0x[a-f0-9]+}} col:16 a 'short' -// CHECK:STDOUT: `-FunctionDecl {{0x[a-f0-9]+}} col:6 foo__carbon_thunk 'void (short * _Nonnull)' extern +// CHECK:STDOUT: |-NamespaceDecl {{0x[a-f0-9]+}} <> implicit std +// CHECK:STDOUT: |-FunctionDecl {{0x[a-f0-9]+}} <> implicit operator new 'void *(__size_t)' +// CHECK:STDOUT: | |-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: | |-ReturnsNonNullAttr {{0x[a-f0-9]+}} <> Implicit +// CHECK:STDOUT: | `-AllocSizeAttr {{0x[a-f0-9]+}} <> Implicit 1 +// CHECK:STDOUT: |-FunctionDecl {{0x[a-f0-9]+}} <> implicit operator new 'void *(__size_t, std::align_val_t)' +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'std::align_val_t' +// CHECK:STDOUT: | |-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: | |-ReturnsNonNullAttr {{0x[a-f0-9]+}} <> Implicit +// CHECK:STDOUT: | |-AllocSizeAttr {{0x[a-f0-9]+}} <> Implicit 1 +// CHECK:STDOUT: | `-AllocAlignAttr {{0x[a-f0-9]+}} <> Implicit 2 +// CHECK:STDOUT: |-FunctionDecl {{0x[a-f0-9]+}} <> implicit operator new[] 'void *(__size_t)' +// CHECK:STDOUT: | |-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: | |-ReturnsNonNullAttr {{0x[a-f0-9]+}} <> Implicit +// CHECK:STDOUT: | `-AllocSizeAttr {{0x[a-f0-9]+}} <> Implicit 1 +// CHECK:STDOUT: |-FunctionDecl {{0x[a-f0-9]+}} <> implicit operator new[] 'void *(__size_t, std::align_val_t)' +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'std::align_val_t' +// CHECK:STDOUT: | |-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: | |-ReturnsNonNullAttr {{0x[a-f0-9]+}} <> Implicit +// CHECK:STDOUT: | |-AllocSizeAttr {{0x[a-f0-9]+}} <> Implicit 1 +// CHECK:STDOUT: | `-AllocAlignAttr {{0x[a-f0-9]+}} <> Implicit 2 +// CHECK:STDOUT: |-FunctionDecl {{0x[a-f0-9]+}} <> implicit operator delete 'void (void *) noexcept' +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'void *' +// CHECK:STDOUT: | `-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: |-FunctionDecl {{0x[a-f0-9]+}} <> implicit operator delete 'void (void *, std::align_val_t) noexcept' +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'void *' +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'std::align_val_t' +// CHECK:STDOUT: | `-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'void *' +// CHECK:STDOUT: | `-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'void *' +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'std::align_val_t' +// CHECK:STDOUT: | `-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: |-FunctionDecl {{0x[a-f0-9]+}} <> implicit operator delete[] 'void (void *) noexcept' +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'void *' +// CHECK:STDOUT: | `-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: |-FunctionDecl {{0x[a-f0-9]+}} <> implicit operator delete[] 'void (void *, std::align_val_t) noexcept' +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'void *' +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'std::align_val_t' +// CHECK:STDOUT: | `-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'void *' +// CHECK:STDOUT: | `-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'void *' +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <> implicit 'std::align_val_t' +// CHECK:STDOUT: | `-VisibilityAttr {{0x[a-f0-9]+}} <> Implicit Default +// CHECK:STDOUT: |-FunctionDecl {{0x[a-f0-9]+}} <:8:1, line:14:1> line:8:7 operator new 'void *(unsigned long, void *) noexcept' +// CHECK:STDOUT: | |-ParmVarDecl {{0x[a-f0-9]+}} <:173:23, col:37> :8:33 'unsigned long' +// CHECK:STDOUT: | `-ParmVarDecl {{0x[a-f0-9]+}} col:40 'void *' +// CHECK:STDOUT: `-FunctionDecl {{0x[a-f0-9]+}} <./thunk_required.h:[[@LINE-51]]:6> col:6 foo__carbon_thunk 'void (short * _Nonnull)' extern // CHECK:STDOUT: |-ParmVarDecl {{0x[a-f0-9]+}} col:6 used a 'short * _Nonnull':'short *' // CHECK:STDOUT: |-ReturnStmt {{0x[a-f0-9]+}} // CHECK:STDOUT: | `-CallExpr {{0x[a-f0-9]+}} 'void' diff --git a/toolchain/check/testdata/interop/cpp/function/union.carbon b/toolchain/check/testdata/interop/cpp/function/union.carbon index 047c425e7c19..5d0a4b8026a8 100644 --- a/toolchain/check/testdata/interop/cpp/function/union.carbon +++ b/toolchain/check/testdata/interop/cpp/function/union.carbon @@ -408,13 +408,29 @@ auto foo() -> U; library "[[@TEST_NAME]]"; +// CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+12]]:10: in file included here [InCppInclude] +// CHECK:STDERR: ./decl_value_return_type.h:4:6: error: calling 'foo' with incomplete return type 'U' [CppInteropParseError] +// CHECK:STDERR: 4 | auto foo() -> U; +// CHECK:STDERR: | ^~~ +// CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+8]]:10: in file included here [InCppInclude] +// CHECK:STDERR: ./decl_value_return_type.h:4:6: note: 'foo' declared here [CppInteropParseNote] +// CHECK:STDERR: 4 | auto foo() -> U; +// CHECK:STDERR: | ^ +// CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+4]]:10: in file included here [InCppInclude] +// CHECK:STDERR: ./decl_value_return_type.h:2:7: note: forward declaration of 'U' [CppInteropParseNote] +// CHECK:STDERR: 2 | union U; +// CHECK:STDERR: | ^ import Cpp library "decl_value_return_type.h"; fn F() { + // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+13]]:3: note: in `Cpp` name lookup for `foo` [InCppNameLookup] + // CHECK:STDERR: Cpp.foo(); + // CHECK:STDERR: ^~~~~~~ + // CHECK:STDERR: // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE+9]]:3: error: function returns incomplete type `Cpp.U` [IncompleteTypeInFunctionReturnType] // CHECK:STDERR: Cpp.foo(); // CHECK:STDERR: ^~~~~~~~~ - // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE-6]]:10: in file included here [InCppInclude] + // CHECK:STDERR: fail_import_decl_value_return_type.carbon:[[@LINE-10]]:10: in file included here [InCppInclude] // CHECK:STDERR: ./decl_value_return_type.h:2:7: note: class was forward declared here [ClassForwardDeclaredHere] // CHECK:STDERR: union U; // CHECK:STDERR: ^ @@ -1104,9 +1120,11 @@ fn F() { // CHECK:STDOUT: %U: type = class_type @U [concrete] // CHECK:STDOUT: %foo.type: type = fn_type @foo [concrete] // CHECK:STDOUT: %foo: %foo.type = struct_value () [concrete] +// CHECK:STDOUT: %ptr.86f: type = ptr_type %U [concrete] +// CHECK:STDOUT: %foo__carbon_thunk.type: type = fn_type @foo__carbon_thunk [concrete] +// CHECK:STDOUT: %foo__carbon_thunk: %foo__carbon_thunk.type = struct_value () [concrete] // CHECK:STDOUT: %T.as.Destroy.impl.Op.type.0b7: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%U) [concrete] // CHECK:STDOUT: %T.as.Destroy.impl.Op.2fa: %T.as.Destroy.impl.Op.type.0b7 = struct_value () [concrete] -// CHECK:STDOUT: %ptr.86f: type = ptr_type %U [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -1119,6 +1137,11 @@ fn F() { // CHECK:STDOUT: } { // CHECK:STDOUT: // CHECK:STDOUT: } +// CHECK:STDOUT: %foo__carbon_thunk.decl: %foo__carbon_thunk.type = fn_decl @foo__carbon_thunk [concrete = constants.%foo__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @F() { @@ -1126,13 +1149,15 @@ fn F() { // CHECK:STDOUT: %Cpp.ref: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo] // CHECK:STDOUT: %.loc8_11.1: ref %U = temporary_storage -// CHECK:STDOUT: %foo.call: init %U = call %foo.ref() to %.loc8_11.1 -// CHECK:STDOUT: %.loc8_11.2: ref %U = temporary %.loc8_11.1, %foo.call +// CHECK:STDOUT: %addr.loc8_11.1: %ptr.86f = addr_of %.loc8_11.1 +// CHECK:STDOUT: %foo__carbon_thunk.call: init %empty_tuple.type = call imports.%foo__carbon_thunk.decl(%addr.loc8_11.1) +// CHECK:STDOUT: %.loc8_11.2: init %U = in_place_init %foo__carbon_thunk.call, %.loc8_11.1 +// CHECK:STDOUT: %.loc8_11.3: ref %U = temporary %.loc8_11.1, %.loc8_11.2 // CHECK:STDOUT: %T.as.Destroy.impl.Op.bound: = bound_method %.loc8_11.1, constants.%T.as.Destroy.impl.Op.2fa // CHECK:STDOUT: // CHECK:STDOUT: %bound_method: = bound_method %.loc8_11.1, %T.as.Destroy.impl.Op.specific_fn -// CHECK:STDOUT: %addr: %ptr.86f = addr_of %.loc8_11.1 -// CHECK:STDOUT: %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr) +// CHECK:STDOUT: %addr.loc8_11.2: %ptr.86f = addr_of %.loc8_11.1 +// CHECK:STDOUT: %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc8_11.2) // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/interop/cpp/namespace.carbon b/toolchain/check/testdata/interop/cpp/namespace.carbon index be8cf7eb8319..2dd6f26930aa 100644 --- a/toolchain/check/testdata/interop/cpp/namespace.carbon +++ b/toolchain/check/testdata/interop/cpp/namespace.carbon @@ -354,9 +354,11 @@ fn Use(y: Cpp.Y) -> i32 { // CHECK:STDOUT: %X: type = class_type @X [concrete] // CHECK:STDOUT: %foo.type: type = fn_type @foo [concrete] // CHECK:STDOUT: %foo: %foo.type = struct_value () [concrete] +// CHECK:STDOUT: %ptr.13d: type = ptr_type %X [concrete] +// CHECK:STDOUT: %foo__carbon_thunk.type: type = fn_type @foo__carbon_thunk [concrete] +// CHECK:STDOUT: %foo__carbon_thunk: %foo__carbon_thunk.type = struct_value () [concrete] // CHECK:STDOUT: %T.as.Destroy.impl.Op.type.0e5: type = fn_type @T.as.Destroy.impl.Op, @T.as.Destroy.impl(%X) [concrete] // CHECK:STDOUT: %T.as.Destroy.impl.Op.cbf: %T.as.Destroy.impl.Op.type.0e5 = struct_value () [concrete] -// CHECK:STDOUT: %ptr.13d: type = ptr_type %X [concrete] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -369,6 +371,11 @@ fn Use(y: Cpp.Y) -> i32 { // CHECK:STDOUT: } { // CHECK:STDOUT: // CHECK:STDOUT: } +// CHECK:STDOUT: %foo__carbon_thunk.decl: %foo__carbon_thunk.type = fn_decl @foo__carbon_thunk [concrete = constants.%foo__carbon_thunk] { +// CHECK:STDOUT: +// CHECK:STDOUT: } { +// CHECK:STDOUT: +// CHECK:STDOUT: } // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: fn @MyF() { @@ -376,13 +383,15 @@ fn Use(y: Cpp.Y) -> i32 { // CHECK:STDOUT: %Cpp.ref: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] // CHECK:STDOUT: %foo.ref: %foo.type = name_ref foo, imports.%foo.decl [concrete = constants.%foo] // CHECK:STDOUT: %.loc8_11.1: ref %X = temporary_storage -// CHECK:STDOUT: %foo.call: init %X = call %foo.ref() to %.loc8_11.1 -// CHECK:STDOUT: %.loc8_11.2: ref %X = temporary %.loc8_11.1, %foo.call +// CHECK:STDOUT: %addr.loc8_11.1: %ptr.13d = addr_of %.loc8_11.1 +// CHECK:STDOUT: %foo__carbon_thunk.call: init %empty_tuple.type = call imports.%foo__carbon_thunk.decl(%addr.loc8_11.1) +// CHECK:STDOUT: %.loc8_11.2: init %X = in_place_init %foo__carbon_thunk.call, %.loc8_11.1 +// CHECK:STDOUT: %.loc8_11.3: ref %X = temporary %.loc8_11.1, %.loc8_11.2 // CHECK:STDOUT: %T.as.Destroy.impl.Op.bound: = bound_method %.loc8_11.1, constants.%T.as.Destroy.impl.Op.cbf // CHECK:STDOUT: // CHECK:STDOUT: %bound_method: = bound_method %.loc8_11.1, %T.as.Destroy.impl.Op.specific_fn -// CHECK:STDOUT: %addr: %ptr.13d = addr_of %.loc8_11.1 -// CHECK:STDOUT: %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr) +// CHECK:STDOUT: %addr.loc8_11.2: %ptr.13d = addr_of %.loc8_11.1 +// CHECK:STDOUT: %T.as.Destroy.impl.Op.call: init %empty_tuple.type = call %bound_method(%addr.loc8_11.2) // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/lower/handle_expr_category.cpp b/toolchain/lower/handle_expr_category.cpp index 5affbe111834..640ec6848db0 100644 --- a/toolchain/lower/handle_expr_category.cpp +++ b/toolchain/lower/handle_expr_category.cpp @@ -35,6 +35,40 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, } } +auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, + SemIR::InPlaceInit inst) -> void { + auto type = context.GetTypeIdOfInst(inst_id); + auto* value = context.GetValue(inst.dest_id); + + // If the initializing representation is by-value, and the value + // representation is by-copy, then we need to load from the storage. Otherwise + // we want a pointer to the result. + switch (context.GetInitRepr(type).kind) { + case SemIR::InitRepr::None: + case SemIR::InitRepr::InPlace: + break; + case SemIR::InitRepr::ByCopy: + switch (context.GetValueRepr(type).repr.kind) { + case SemIR::ValueRepr::Unknown: + CARBON_FATAL("Unexpected incomplete type"); + case SemIR::ValueRepr::None: + case SemIR::ValueRepr::Pointer: + break; + case SemIR::ValueRepr::Copy: + value = context.builder().CreateLoad(context.GetType(type), value); + break; + case SemIR::ValueRepr::Custom: + CARBON_FATAL( + "TODO: Add support for InPlaceInit with custom value rep"); + } + break; + case SemIR::InitRepr::Incomplete: + CARBON_FATAL("Unexpected incomplete type"); + } + + context.SetLocal(inst_id, value); +} + auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, SemIR::Temporary inst) -> void { context.FinishInit(context.GetTypeIdOfInst(inst_id), inst.storage_id, diff --git a/toolchain/lower/testdata/interop/cpp/method.carbon b/toolchain/lower/testdata/interop/cpp/method.carbon index cb1ff61790c0..209f9379727e 100644 --- a/toolchain/lower/testdata/interop/cpp/method.carbon +++ b/toolchain/lower/testdata/interop/cpp/method.carbon @@ -68,8 +68,8 @@ fn Call(n: Cpp.NeedThunk) { // CHECK:STDOUT: // CHECK:STDOUT: define i32 @_CUseVal.Main(ptr %a) !dbg !7 { // CHECK:STDOUT: entry: -// CHECK:STDOUT: %A.by_val.call = call i32 @_ZNK1A6by_valEv(ptr %a), !dbg !10 -// CHECK:STDOUT: ret i32 %A.by_val.call, !dbg !11 +// CHECK:STDOUT: %by_val__carbon_thunk.call = call i32 @_ZNK1A6by_valEv.carbon_thunk(ptr %a), !dbg !10 +// CHECK:STDOUT: ret i32 %by_val__carbon_thunk.call, !dbg !11 // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone @@ -83,7 +83,18 @@ fn Call(n: Cpp.NeedThunk) { // CHECK:STDOUT: ret i32 %0 // CHECK:STDOUT: } // CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress +// CHECK:STDOUT: define dso_local i32 @_ZNK1A6by_valEv.carbon_thunk(ptr %this) #1 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %this.addr = alloca ptr, align 8 +// CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8 +// CHECK:STDOUT: %0 = load ptr, ptr %this.addr, align 8 +// CHECK:STDOUT: %call = call i32 @_ZNK1A6by_valEv(ptr nonnull align 4 dereferenceable(4) %0) +// CHECK:STDOUT: ret i32 %call +// CHECK:STDOUT: } +// CHECK:STDOUT: // CHECK:STDOUT: attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: attributes #1 = { alwaysinline mustprogress "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } // CHECK:STDOUT: // CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4} // CHECK:STDOUT: !llvm.dbg.cu = !{!5} diff --git a/toolchain/lower/testdata/interop/cpp/thunk.carbon b/toolchain/lower/testdata/interop/cpp/parameters.carbon similarity index 99% rename from toolchain/lower/testdata/interop/cpp/thunk.carbon rename to toolchain/lower/testdata/interop/cpp/parameters.carbon index 9279865e61a4..e48e0bad1fde 100644 --- a/toolchain/lower/testdata/interop/cpp/thunk.carbon +++ b/toolchain/lower/testdata/interop/cpp/parameters.carbon @@ -6,9 +6,9 @@ // // AUTOUPDATE // TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/thunk.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/parameters.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/thunk.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/parameters.carbon // ============================================================================ // Parameter requires a thunk to be generated diff --git a/toolchain/lower/testdata/interop/cpp/return.carbon b/toolchain/lower/testdata/interop/cpp/return.carbon new file mode 100644 index 000000000000..dcbcc43ba45e --- /dev/null +++ b/toolchain/lower/testdata/interop/cpp/return.carbon @@ -0,0 +1,465 @@ +// 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-FILE: toolchain/testing/testdata/min_prelude/primitives.carbon +// EXTRA-ARGS: --target=x86_64-linux-gnu +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/return.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/return.carbon + +// --- ints.h + +unsigned char ReturnU8(); +unsigned short ReturnU16(); +unsigned int ReturnU32(); +unsigned long ReturnU64(); + +signed char ReturnI8(); +signed short ReturnI16(); +signed int ReturnI32(); +signed long ReturnI64(); + +// --- import_ints.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "ints.h"; + +fn GetU8() -> u8 { return Cpp.ReturnU8(); } +fn GetU16() -> u16 { return Cpp.ReturnU16(); } +fn GetU32() -> u32 { return Cpp.ReturnU32(); } +fn GetU64() -> u64 { return Cpp.ReturnU64(); } + +fn GetI8() -> i8 { return Cpp.ReturnI8(); } +fn GetI16() -> i16 { return Cpp.ReturnI16(); } +fn GetI32() -> i32 { return Cpp.ReturnI32(); } +fn GetI64() -> i64 { return Cpp.ReturnI64(); } + +fn Use(a: u8, b: u16, c: u32, d: u64, e: i8, f: i16, g: i32, h: i64); + +fn Lets() { + let my_u8: u8 = Cpp.ReturnU8(); + let my_u16: u16 = Cpp.ReturnU16(); + let my_u32: u32 = Cpp.ReturnU32(); + let my_u64: u64 = Cpp.ReturnU64(); + + let my_i8: i8 = Cpp.ReturnI8(); + let my_i16: i16 = Cpp.ReturnI16(); + let my_i32: i32 = Cpp.ReturnI32(); + let my_i64: i64 = Cpp.ReturnI64(); + + Use(my_u8, my_u16, my_u32, my_u64, my_i8, my_i16, my_i32, my_i64); +} + +fn Vars() { + var my_u8: u8 = Cpp.ReturnU8(); + var my_u16: u16 = Cpp.ReturnU16(); + var my_u32: u32 = Cpp.ReturnU32(); + var my_u64: u64 = Cpp.ReturnU64(); + + var my_i8: i8 = Cpp.ReturnI8(); + var my_i16: i16 = Cpp.ReturnI16(); + var my_i32: i32 = Cpp.ReturnI32(); + var my_i64: i64 = Cpp.ReturnI64(); + + Use(my_u8, my_u16, my_u32, my_u64, my_i8, my_i16, my_i32, my_i64); +} + +// --- class.h + +struct X { + X(); + X(const X&); + ~X(); + + void *p, *q; +}; + +X Make(); + +// --- import_class.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "class.h"; + +fn GetX() -> Cpp.X { return Cpp.Make(); } + +fn Let() { + let x: Cpp.X = Cpp.Make(); +} + +fn Var() { + var x: Cpp.X = Cpp.Make(); +} + +// CHECK:STDOUT: ; ModuleID = 'import_ints.carbon' +// CHECK:STDOUT: source_filename = "import_ints.carbon" +// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu" +// CHECK:STDOUT: +// CHECK:STDOUT: define i8 @_CGetU8.Main() !dbg !7 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.loc6_40.1.temp = alloca i8, align 1, !dbg !10 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc6_40.1.temp), !dbg !10 +// CHECK:STDOUT: call void @_Z8ReturnU8v.carbon_thunk(ptr %.loc6_40.1.temp), !dbg !10 +// CHECK:STDOUT: %.loc6_40.2 = load i8, ptr %.loc6_40.1.temp, align 1, !dbg !10 +// CHECK:STDOUT: ret i8 %.loc6_40.2, !dbg !11 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare i8 @_Z8ReturnU8v() +// CHECK:STDOUT: +// CHECK:STDOUT: define i16 @_CGetU16.Main() !dbg !12 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.loc7_43.1.temp = alloca i16, align 2, !dbg !13 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc7_43.1.temp), !dbg !13 +// CHECK:STDOUT: call void @_Z9ReturnU16v.carbon_thunk(ptr %.loc7_43.1.temp), !dbg !13 +// CHECK:STDOUT: %.loc7_43.2 = load i16, ptr %.loc7_43.1.temp, align 2, !dbg !13 +// CHECK:STDOUT: ret i16 %.loc7_43.2, !dbg !14 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare i16 @_Z9ReturnU16v() +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @_CGetU32.Main() !dbg !15 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %ReturnU32.call = call i32 @_Z9ReturnU32v(), !dbg !16 +// CHECK:STDOUT: ret i32 %ReturnU32.call, !dbg !17 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare i32 @_Z9ReturnU32v() +// CHECK:STDOUT: +// CHECK:STDOUT: define i64 @_CGetU64.Main() !dbg !18 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %ReturnU64.call = call i64 @_Z9ReturnU64v(), !dbg !19 +// CHECK:STDOUT: ret i64 %ReturnU64.call, !dbg !20 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare i64 @_Z9ReturnU64v() +// CHECK:STDOUT: +// CHECK:STDOUT: define i8 @_CGetI8.Main() !dbg !21 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.loc11_40.1.temp = alloca i8, align 1, !dbg !22 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc11_40.1.temp), !dbg !22 +// CHECK:STDOUT: call void @_Z8ReturnI8v.carbon_thunk(ptr %.loc11_40.1.temp), !dbg !22 +// CHECK:STDOUT: %.loc11_40.2 = load i8, ptr %.loc11_40.1.temp, align 1, !dbg !22 +// CHECK:STDOUT: ret i8 %.loc11_40.2, !dbg !23 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare i8 @_Z8ReturnI8v() +// CHECK:STDOUT: +// CHECK:STDOUT: define i16 @_CGetI16.Main() !dbg !24 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.loc12_43.1.temp = alloca i16, align 2, !dbg !25 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc12_43.1.temp), !dbg !25 +// CHECK:STDOUT: call void @_Z9ReturnI16v.carbon_thunk(ptr %.loc12_43.1.temp), !dbg !25 +// CHECK:STDOUT: %.loc12_43.2 = load i16, ptr %.loc12_43.1.temp, align 2, !dbg !25 +// CHECK:STDOUT: ret i16 %.loc12_43.2, !dbg !26 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare i16 @_Z9ReturnI16v() +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @_CGetI32.Main() !dbg !27 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %ReturnI32.call = call i32 @_Z9ReturnI32v(), !dbg !28 +// CHECK:STDOUT: ret i32 %ReturnI32.call, !dbg !29 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare i32 @_Z9ReturnI32v() +// CHECK:STDOUT: +// CHECK:STDOUT: define i64 @_CGetI64.Main() !dbg !30 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %ReturnI64.call = call i64 @_Z9ReturnI64v(), !dbg !31 +// CHECK:STDOUT: ret i64 %ReturnI64.call, !dbg !32 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare i64 @_Z9ReturnI64v() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CUse.Main(i8, i16, i32, i64, i8, i16, i32, i64) +// CHECK:STDOUT: +// CHECK:STDOUT: define void @_CLets.Main() !dbg !33 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.loc19_32.1.temp = alloca i8, align 1, !dbg !34 +// CHECK:STDOUT: %.loc20_35.1.temp = alloca i16, align 2, !dbg !35 +// CHECK:STDOUT: %.loc24_32.1.temp = alloca i8, align 1, !dbg !36 +// CHECK:STDOUT: %.loc25_35.1.temp = alloca i16, align 2, !dbg !37 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc19_32.1.temp), !dbg !34 +// CHECK:STDOUT: call void @_Z8ReturnU8v.carbon_thunk(ptr %.loc19_32.1.temp), !dbg !34 +// CHECK:STDOUT: %.loc19_32.2 = load i8, ptr %.loc19_32.1.temp, align 1, !dbg !34 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc20_35.1.temp), !dbg !35 +// CHECK:STDOUT: call void @_Z9ReturnU16v.carbon_thunk(ptr %.loc20_35.1.temp), !dbg !35 +// CHECK:STDOUT: %.loc20_35.2 = load i16, ptr %.loc20_35.1.temp, align 2, !dbg !35 +// CHECK:STDOUT: %ReturnU32.call = call i32 @_Z9ReturnU32v(), !dbg !38 +// CHECK:STDOUT: %ReturnU64.call = call i64 @_Z9ReturnU64v(), !dbg !39 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc24_32.1.temp), !dbg !36 +// CHECK:STDOUT: call void @_Z8ReturnI8v.carbon_thunk(ptr %.loc24_32.1.temp), !dbg !36 +// CHECK:STDOUT: %.loc24_32.2 = load i8, ptr %.loc24_32.1.temp, align 1, !dbg !36 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc25_35.1.temp), !dbg !37 +// CHECK:STDOUT: call void @_Z9ReturnI16v.carbon_thunk(ptr %.loc25_35.1.temp), !dbg !37 +// CHECK:STDOUT: %.loc25_35.2 = load i16, ptr %.loc25_35.1.temp, align 2, !dbg !37 +// CHECK:STDOUT: %ReturnI32.call = call i32 @_Z9ReturnI32v(), !dbg !40 +// CHECK:STDOUT: %ReturnI64.call = call i64 @_Z9ReturnI64v(), !dbg !41 +// CHECK:STDOUT: call void @_CUse.Main(i8 %.loc19_32.2, i16 %.loc20_35.2, i32 %ReturnU32.call, i64 %ReturnU64.call, i8 %.loc24_32.2, i16 %.loc25_35.2, i32 %ReturnI32.call, i64 %ReturnI64.call), !dbg !42 +// CHECK:STDOUT: ret void, !dbg !43 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define void @_CVars.Main() !dbg !44 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %my_u8.var = alloca i8, align 1, !dbg !45 +// CHECK:STDOUT: %.loc33_32.1.temp = alloca i8, align 1, !dbg !46 +// CHECK:STDOUT: %my_u16.var = alloca i16, align 2, !dbg !47 +// CHECK:STDOUT: %.loc34_35.1.temp = alloca i16, align 2, !dbg !48 +// CHECK:STDOUT: %my_u32.var = alloca i32, align 4, !dbg !49 +// CHECK:STDOUT: %my_u64.var = alloca i64, align 8, !dbg !50 +// CHECK:STDOUT: %my_i8.var = alloca i8, align 1, !dbg !51 +// CHECK:STDOUT: %.loc38_32.1.temp = alloca i8, align 1, !dbg !52 +// CHECK:STDOUT: %my_i16.var = alloca i16, align 2, !dbg !53 +// CHECK:STDOUT: %.loc39_35.1.temp = alloca i16, align 2, !dbg !54 +// CHECK:STDOUT: %my_i32.var = alloca i32, align 4, !dbg !55 +// CHECK:STDOUT: %my_i64.var = alloca i64, align 8, !dbg !56 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %my_u8.var), !dbg !45 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc33_32.1.temp), !dbg !46 +// CHECK:STDOUT: call void @_Z8ReturnU8v.carbon_thunk(ptr %.loc33_32.1.temp), !dbg !46 +// CHECK:STDOUT: %.loc33_32.2 = load i8, ptr %.loc33_32.1.temp, align 1, !dbg !46 +// CHECK:STDOUT: store i8 %.loc33_32.2, ptr %my_u8.var, align 1, !dbg !45 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %my_u16.var), !dbg !47 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc34_35.1.temp), !dbg !48 +// CHECK:STDOUT: call void @_Z9ReturnU16v.carbon_thunk(ptr %.loc34_35.1.temp), !dbg !48 +// CHECK:STDOUT: %.loc34_35.2 = load i16, ptr %.loc34_35.1.temp, align 2, !dbg !48 +// CHECK:STDOUT: store i16 %.loc34_35.2, ptr %my_u16.var, align 2, !dbg !47 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %my_u32.var), !dbg !49 +// CHECK:STDOUT: %ReturnU32.call = call i32 @_Z9ReturnU32v(), !dbg !57 +// CHECK:STDOUT: store i32 %ReturnU32.call, ptr %my_u32.var, align 4, !dbg !49 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %my_u64.var), !dbg !50 +// CHECK:STDOUT: %ReturnU64.call = call i64 @_Z9ReturnU64v(), !dbg !58 +// CHECK:STDOUT: store i64 %ReturnU64.call, ptr %my_u64.var, align 4, !dbg !50 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %my_i8.var), !dbg !51 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc38_32.1.temp), !dbg !52 +// CHECK:STDOUT: call void @_Z8ReturnI8v.carbon_thunk(ptr %.loc38_32.1.temp), !dbg !52 +// CHECK:STDOUT: %.loc38_32.2 = load i8, ptr %.loc38_32.1.temp, align 1, !dbg !52 +// CHECK:STDOUT: store i8 %.loc38_32.2, ptr %my_i8.var, align 1, !dbg !51 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %my_i16.var), !dbg !53 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc39_35.1.temp), !dbg !54 +// CHECK:STDOUT: call void @_Z9ReturnI16v.carbon_thunk(ptr %.loc39_35.1.temp), !dbg !54 +// CHECK:STDOUT: %.loc39_35.2 = load i16, ptr %.loc39_35.1.temp, align 2, !dbg !54 +// CHECK:STDOUT: store i16 %.loc39_35.2, ptr %my_i16.var, align 2, !dbg !53 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %my_i32.var), !dbg !55 +// CHECK:STDOUT: %ReturnI32.call = call i32 @_Z9ReturnI32v(), !dbg !59 +// CHECK:STDOUT: store i32 %ReturnI32.call, ptr %my_i32.var, align 4, !dbg !55 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %my_i64.var), !dbg !56 +// CHECK:STDOUT: %ReturnI64.call = call i64 @_Z9ReturnI64v(), !dbg !60 +// CHECK:STDOUT: store i64 %ReturnI64.call, ptr %my_i64.var, align 4, !dbg !56 +// CHECK:STDOUT: %.loc43_7 = load i8, ptr %my_u8.var, align 1, !dbg !61 +// CHECK:STDOUT: %.loc43_14 = load i16, ptr %my_u16.var, align 2, !dbg !62 +// CHECK:STDOUT: %.loc43_22 = load i32, ptr %my_u32.var, align 4, !dbg !63 +// CHECK:STDOUT: %.loc43_30 = load i64, ptr %my_u64.var, align 4, !dbg !64 +// CHECK:STDOUT: %.loc43_38 = load i8, ptr %my_i8.var, align 1, !dbg !65 +// CHECK:STDOUT: %.loc43_45 = load i16, ptr %my_i16.var, align 2, !dbg !66 +// CHECK:STDOUT: %.loc43_53 = load i32, ptr %my_i32.var, align 4, !dbg !67 +// CHECK:STDOUT: %.loc43_61 = load i64, ptr %my_i64.var, align 4, !dbg !68 +// CHECK:STDOUT: call void @_CUse.Main(i8 %.loc43_7, i16 %.loc43_14, i32 %.loc43_22, i64 %.loc43_30, i8 %.loc43_38, i16 %.loc43_45, i32 %.loc43_53, i64 %.loc43_61), !dbg !69 +// CHECK:STDOUT: ret void, !dbg !70 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #0 +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress +// CHECK:STDOUT: define dso_local void @_Z8ReturnU8v.carbon_thunk(ptr %return) #1 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %return.addr = alloca ptr, align 8 +// CHECK:STDOUT: store ptr %return, ptr %return.addr, align 8 +// CHECK:STDOUT: %0 = load ptr, ptr %return.addr, align 8 +// CHECK:STDOUT: %call = call zeroext i8 @_Z8ReturnU8v() +// CHECK:STDOUT: store i8 %call, ptr %0, align 1 +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress +// CHECK:STDOUT: define dso_local void @_Z9ReturnU16v.carbon_thunk(ptr %return) #1 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %return.addr = alloca ptr, align 8 +// CHECK:STDOUT: store ptr %return, ptr %return.addr, align 8 +// CHECK:STDOUT: %0 = load ptr, ptr %return.addr, align 8 +// CHECK:STDOUT: %call = call zeroext i16 @_Z9ReturnU16v() +// CHECK:STDOUT: store i16 %call, ptr %0, align 2 +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress +// CHECK:STDOUT: define dso_local void @_Z8ReturnI8v.carbon_thunk(ptr %return) #1 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %return.addr = alloca ptr, align 8 +// CHECK:STDOUT: store ptr %return, ptr %return.addr, align 8 +// CHECK:STDOUT: %0 = load ptr, ptr %return.addr, align 8 +// CHECK:STDOUT: %call = call signext i8 @_Z8ReturnI8v() +// CHECK:STDOUT: store i8 %call, ptr %0, align 1 +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress +// CHECK:STDOUT: define dso_local void @_Z9ReturnI16v.carbon_thunk(ptr %return) #1 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %return.addr = alloca ptr, align 8 +// CHECK:STDOUT: store ptr %return, ptr %return.addr, align 8 +// CHECK:STDOUT: %0 = load ptr, ptr %return.addr, align 8 +// CHECK:STDOUT: %call = call signext i16 @_Z9ReturnI16v() +// CHECK:STDOUT: store i16 %call, ptr %0, align 2 +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; uselistorder directives +// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 } +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +// CHECK:STDOUT: attributes #1 = { alwaysinline mustprogress "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4} +// CHECK:STDOUT: !llvm.dbg.cu = !{!5} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4} +// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0} +// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2} +// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !6 = !DIFile(filename: "import_ints.carbon", directory: "") +// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "GetU8", linkageName: "_CGetU8.Main", scope: null, file: !6, line: 6, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !8 = !DISubroutineType(types: !9) +// CHECK:STDOUT: !9 = !{} +// CHECK:STDOUT: !10 = !DILocation(line: 6, column: 27, scope: !7) +// CHECK:STDOUT: !11 = !DILocation(line: 6, column: 20, scope: !7) +// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "GetU16", linkageName: "_CGetU16.Main", scope: null, file: !6, line: 7, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !13 = !DILocation(line: 7, column: 29, scope: !12) +// CHECK:STDOUT: !14 = !DILocation(line: 7, column: 22, scope: !12) +// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "GetU32", linkageName: "_CGetU32.Main", scope: null, file: !6, line: 8, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !16 = !DILocation(line: 8, column: 29, scope: !15) +// CHECK:STDOUT: !17 = !DILocation(line: 8, column: 22, scope: !15) +// CHECK:STDOUT: !18 = distinct !DISubprogram(name: "GetU64", linkageName: "_CGetU64.Main", scope: null, file: !6, line: 9, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !19 = !DILocation(line: 9, column: 29, scope: !18) +// CHECK:STDOUT: !20 = !DILocation(line: 9, column: 22, scope: !18) +// CHECK:STDOUT: !21 = distinct !DISubprogram(name: "GetI8", linkageName: "_CGetI8.Main", scope: null, file: !6, line: 11, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !22 = !DILocation(line: 11, column: 27, scope: !21) +// CHECK:STDOUT: !23 = !DILocation(line: 11, column: 20, scope: !21) +// CHECK:STDOUT: !24 = distinct !DISubprogram(name: "GetI16", linkageName: "_CGetI16.Main", scope: null, file: !6, line: 12, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !25 = !DILocation(line: 12, column: 29, scope: !24) +// CHECK:STDOUT: !26 = !DILocation(line: 12, column: 22, scope: !24) +// CHECK:STDOUT: !27 = distinct !DISubprogram(name: "GetI32", linkageName: "_CGetI32.Main", scope: null, file: !6, line: 13, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !28 = !DILocation(line: 13, column: 29, scope: !27) +// CHECK:STDOUT: !29 = !DILocation(line: 13, column: 22, scope: !27) +// CHECK:STDOUT: !30 = distinct !DISubprogram(name: "GetI64", linkageName: "_CGetI64.Main", scope: null, file: !6, line: 14, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !31 = !DILocation(line: 14, column: 29, scope: !30) +// CHECK:STDOUT: !32 = !DILocation(line: 14, column: 22, scope: !30) +// CHECK:STDOUT: !33 = distinct !DISubprogram(name: "Lets", linkageName: "_CLets.Main", scope: null, file: !6, line: 18, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !34 = !DILocation(line: 19, column: 19, scope: !33) +// CHECK:STDOUT: !35 = !DILocation(line: 20, column: 21, scope: !33) +// CHECK:STDOUT: !36 = !DILocation(line: 24, column: 19, scope: !33) +// CHECK:STDOUT: !37 = !DILocation(line: 25, column: 21, scope: !33) +// CHECK:STDOUT: !38 = !DILocation(line: 21, column: 21, scope: !33) +// CHECK:STDOUT: !39 = !DILocation(line: 22, column: 21, scope: !33) +// CHECK:STDOUT: !40 = !DILocation(line: 26, column: 21, scope: !33) +// CHECK:STDOUT: !41 = !DILocation(line: 27, column: 21, scope: !33) +// CHECK:STDOUT: !42 = !DILocation(line: 29, column: 3, scope: !33) +// CHECK:STDOUT: !43 = !DILocation(line: 18, column: 1, scope: !33) +// CHECK:STDOUT: !44 = distinct !DISubprogram(name: "Vars", linkageName: "_CVars.Main", scope: null, file: !6, line: 32, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !45 = !DILocation(line: 33, column: 3, scope: !44) +// CHECK:STDOUT: !46 = !DILocation(line: 33, column: 19, scope: !44) +// CHECK:STDOUT: !47 = !DILocation(line: 34, column: 3, scope: !44) +// CHECK:STDOUT: !48 = !DILocation(line: 34, column: 21, scope: !44) +// CHECK:STDOUT: !49 = !DILocation(line: 35, column: 3, scope: !44) +// CHECK:STDOUT: !50 = !DILocation(line: 36, column: 3, scope: !44) +// CHECK:STDOUT: !51 = !DILocation(line: 38, column: 3, scope: !44) +// CHECK:STDOUT: !52 = !DILocation(line: 38, column: 19, scope: !44) +// CHECK:STDOUT: !53 = !DILocation(line: 39, column: 3, scope: !44) +// CHECK:STDOUT: !54 = !DILocation(line: 39, column: 21, scope: !44) +// CHECK:STDOUT: !55 = !DILocation(line: 40, column: 3, scope: !44) +// CHECK:STDOUT: !56 = !DILocation(line: 41, column: 3, scope: !44) +// CHECK:STDOUT: !57 = !DILocation(line: 35, column: 21, scope: !44) +// CHECK:STDOUT: !58 = !DILocation(line: 36, column: 21, scope: !44) +// CHECK:STDOUT: !59 = !DILocation(line: 40, column: 21, scope: !44) +// CHECK:STDOUT: !60 = !DILocation(line: 41, column: 21, scope: !44) +// CHECK:STDOUT: !61 = !DILocation(line: 43, column: 7, scope: !44) +// CHECK:STDOUT: !62 = !DILocation(line: 43, column: 14, scope: !44) +// CHECK:STDOUT: !63 = !DILocation(line: 43, column: 22, scope: !44) +// CHECK:STDOUT: !64 = !DILocation(line: 43, column: 30, scope: !44) +// CHECK:STDOUT: !65 = !DILocation(line: 43, column: 38, scope: !44) +// CHECK:STDOUT: !66 = !DILocation(line: 43, column: 45, scope: !44) +// CHECK:STDOUT: !67 = !DILocation(line: 43, column: 53, scope: !44) +// CHECK:STDOUT: !68 = !DILocation(line: 43, column: 61, scope: !44) +// CHECK:STDOUT: !69 = !DILocation(line: 43, column: 3, scope: !44) +// CHECK:STDOUT: !70 = !DILocation(line: 32, column: 1, scope: !44) +// CHECK:STDOUT: ; ModuleID = 'import_class.carbon' +// CHECK:STDOUT: source_filename = "import_class.carbon" +// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu" +// CHECK:STDOUT: +// CHECK:STDOUT: %struct.X = type { ptr, ptr } +// CHECK:STDOUT: +// CHECK:STDOUT: define void @_CGetX.Main(ptr sret([16 x i8]) %return) !dbg !7 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @_Z4Makev.carbon_thunk(ptr %return), !dbg !10 +// CHECK:STDOUT: ret void, !dbg !11 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_Z4Makev(ptr sret([16 x i8])) +// CHECK:STDOUT: +// CHECK:STDOUT: define void @_CLet.Main() !dbg !12 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %.loc9_27.1.temp = alloca [16 x i8], align 1, !dbg !13 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %.loc9_27.1.temp), !dbg !13 +// CHECK:STDOUT: call void @_Z4Makev.carbon_thunk(ptr %.loc9_27.1.temp), !dbg !13 +// CHECK:STDOUT: ret void, !dbg !14 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define void @_CVar.Main() !dbg !15 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %x.var = alloca [16 x i8], align 1, !dbg !16 +// CHECK:STDOUT: call void @llvm.lifetime.start.p0(ptr %x.var), !dbg !16 +// CHECK:STDOUT: call void @_Z4Makev.carbon_thunk(ptr %x.var), !dbg !17 +// CHECK:STDOUT: ret void, !dbg !18 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +// CHECK:STDOUT: declare void @llvm.lifetime.start.p0(ptr captures(none)) #0 +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress +// CHECK:STDOUT: define dso_local void @_Z4Makev.carbon_thunk(ptr %return) #1 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %return.addr = alloca ptr, align 8 +// CHECK:STDOUT: store ptr %return, ptr %return.addr, align 8 +// CHECK:STDOUT: %0 = load ptr, ptr %return.addr, align 8 +// CHECK:STDOUT: call void @_Z4Makev(ptr dead_on_unwind writable sret(%struct.X) align 8 %0) +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; uselistorder directives +// CHECK:STDOUT: uselistorder ptr @llvm.lifetime.start.p0, { 1, 0 } +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +// CHECK:STDOUT: attributes #1 = { alwaysinline mustprogress "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="0" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4} +// CHECK:STDOUT: !llvm.dbg.cu = !{!5} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4} +// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0} +// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2} +// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !6 = !DIFile(filename: "import_class.carbon", directory: "") +// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "GetX", linkageName: "_CGetX.Main", scope: null, file: !6, line: 6, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !8 = !DISubroutineType(types: !9) +// CHECK:STDOUT: !9 = !{} +// CHECK:STDOUT: !10 = !DILocation(line: 6, column: 29, scope: !7) +// CHECK:STDOUT: !11 = !DILocation(line: 6, column: 22, scope: !7) +// CHECK:STDOUT: !12 = distinct !DISubprogram(name: "Let", linkageName: "_CLet.Main", scope: null, file: !6, line: 8, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !13 = !DILocation(line: 9, column: 18, scope: !12) +// CHECK:STDOUT: !14 = !DILocation(line: 8, column: 1, scope: !12) +// CHECK:STDOUT: !15 = distinct !DISubprogram(name: "Var", linkageName: "_CVar.Main", scope: null, file: !6, line: 12, type: !8, spFlags: DISPFlagDefinition, unit: !5) +// CHECK:STDOUT: !16 = !DILocation(line: 13, column: 3, scope: !15) +// CHECK:STDOUT: !17 = !DILocation(line: 13, column: 18, scope: !15) +// CHECK:STDOUT: !18 = !DILocation(line: 12, column: 1, scope: !15) diff --git a/toolchain/sem_ir/expr_info.cpp b/toolchain/sem_ir/expr_info.cpp index 5fce222e5279..f0e6be07387e 100644 --- a/toolchain/sem_ir/expr_info.cpp +++ b/toolchain/sem_ir/expr_info.cpp @@ -221,6 +221,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory { case ArrayInit::Kind: case Call::Kind: case InitializeFrom::Kind: + case InPlaceInit::Kind: case ClassInit::Kind: case StructInit::Kind: case TupleInit::Kind: @@ -273,6 +274,12 @@ auto FindReturnSlotArgForInitializer(const File& sem_ir, InstId init_id) case CARBON_KIND(InitializeFrom init): { return init.dest_id; } + case CARBON_KIND(InPlaceInit init): { + if (!ReturnTypeInfo::ForType(sem_ir, init.type_id).has_return_slot()) { + return InstId::None; + } + return init.dest_id; + } case CARBON_KIND(Call call): { if (!ReturnTypeInfo::ForType(sem_ir, call.type_id).has_return_slot()) { return InstId::None; diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index 4533cb06de76..5a3fc3fc4255 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/toolchain/sem_ir/inst_kind.def @@ -86,6 +86,7 @@ CARBON_SEM_IR_INST_KIND(ImportCppDecl) CARBON_SEM_IR_INST_KIND(ImportDecl) CARBON_SEM_IR_INST_KIND(ImportRefLoaded) CARBON_SEM_IR_INST_KIND(ImportRefUnloaded) +CARBON_SEM_IR_INST_KIND(InPlaceInit) CARBON_SEM_IR_INST_KIND(InitializeFrom) CARBON_SEM_IR_INST_KIND(InstType) CARBON_SEM_IR_INST_KIND(InstValue) diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index c6fe12122411..96dc076d0321 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -1017,6 +1017,26 @@ struct ImportRefLoaded { EntityNameId entity_name_id; }; +// Tracks that an object has been initialized in-place to form the result of +// this expression, even if its type's initializing representation is not +// normally in-place. If the type does not use in-place initialization, +// initialization from this expression will copy the value out of the +// destination. +// +// This is used to model the initialization performed by C++ thunks, where +// in-place initialization is used even for types that would normally have a +// copy initializing representation. +struct InPlaceInit { + static constexpr auto Kind = InstKind::InPlaceInit.Define( + {.ir_name = "in_place_init", .constant_kind = InstConstantKind::Never}); + + TypeId type_id; + // Used only to track the source of the initialization; this has no semantic + // meaning. + InstId src_id; + DestInstId dest_id; +}; + // Finalizes the initialization of `dest_id` from the initializer expression // `src_id`, by performing a final copy from source to destination, for types // whose initialization is not in-place.