From e06eb8f532a027b1b24442ba307505e125970459 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 20 Mar 2026 09:50:37 -0700 Subject: [PATCH] Create a placement `operator new` directly. (#6941) Instead of injecting code to declare an `operator new`, generate AST for it directly. In order to use this, directly generate a `CXXNewExpr` rather than asking Clang to build one. This is less of a hack, and doesn't visibly leak an `operator new` declaration that inline C++ code or template instantiations might see. It also avoids generating a warning in C++26 and later that the `constexpr` declaration of `operator new` is used but not defined. Assisted-by: Gemini 3.1 Pro via Antigravity --- toolchain/check/cpp/context.h | 10 +++ toolchain/check/cpp/generate_ast.cpp | 28 ------- toolchain/check/cpp/thunk.cpp | 63 ++++++++++++++-- .../interop/cpp/function/thunk_ast.carbon | 75 +++++++------------ .../interop/cpp/function/thunk_cpp26.carbon | 28 +++++++ 5 files changed, 120 insertions(+), 84 deletions(-) create mode 100644 toolchain/check/testdata/interop/cpp/function/thunk_cpp26.carbon diff --git a/toolchain/check/cpp/context.h b/toolchain/check/cpp/context.h index 92234f3bde84..2cce3d4f3f58 100644 --- a/toolchain/check/cpp/context.h +++ b/toolchain/check/cpp/context.h @@ -37,6 +37,13 @@ class CppContext { return carbon_file_locations_; } + auto placement_new_decl() const -> clang::FunctionDecl* { + return placement_new_decl_; + } + void set_placement_new_decl(clang::FunctionDecl* decl) { + placement_new_decl_ = decl; + } + private: // The Clang AST context. clang::ASTContext* ast_context_; @@ -53,6 +60,9 @@ class CppContext { // The Clang mangle context for the target in the ASTContext. std::unique_ptr clang_mangle_context_; + + // The cached placement new function declaration. + clang::FunctionDecl* placement_new_decl_ = nullptr; }; } // namespace Carbon::Check diff --git a/toolchain/check/cpp/generate_ast.cpp b/toolchain/check/cpp/generate_ast.cpp index 8e064057765b..4b7f4b0f9612 100644 --- a/toolchain/check/cpp/generate_ast.cpp +++ b/toolchain/check/cpp/generate_ast.cpp @@ -86,34 +86,6 @@ static auto GenerateCppIncludesHeaderCode( } } } - - // 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/cpp/thunk.cpp b/toolchain/check/cpp/thunk.cpp index 0e4e4eb94e00..1eb877597389 100644 --- a/toolchain/check/cpp/thunk.cpp +++ b/toolchain/check/cpp/thunk.cpp @@ -14,6 +14,7 @@ #include "toolchain/check/context.h" #include "toolchain/check/control_flow.h" #include "toolchain/check/convert.h" +#include "toolchain/check/cpp/context.h" #include "toolchain/check/literal.h" #include "toolchain/check/type.h" #include "toolchain/check/type_completion.h" @@ -23,6 +24,39 @@ namespace Carbon::Check { +// Generate and return a function: +// `void* operator new(__SIZE_TYPE__, void*) noexcept`. +static auto GeneratePlacementNewFunctionDecl(clang::ASTContext& context) + -> clang::FunctionDecl* { + clang::QualType size_type = context.getSizeType(); + clang::QualType void_ptr_type = context.VoidPtrTy; + + auto ext_info = clang::FunctionProtoType::ExtProtoInfo(); + ext_info.ExceptionSpec.Type = clang::EST_BasicNoexcept; + + clang::QualType function_type = context.getFunctionType( + void_ptr_type, {size_type, void_ptr_type}, ext_info); + + clang::DeclarationName name = + context.DeclarationNames.getCXXOperatorName(clang::OO_New); + + clang::FunctionDecl* function_decl = clang::FunctionDecl::Create( + context, context.getTranslationUnitDecl(), clang::SourceLocation(), + clang::SourceLocation(), name, function_type, + /*TInfo=*/nullptr, clang::SC_None); + + clang::ParmVarDecl* size_param = clang::ParmVarDecl::Create( + context, function_decl, clang::SourceLocation(), clang::SourceLocation(), + nullptr, size_type, nullptr, clang::SC_None, nullptr); + clang::ParmVarDecl* ptr_param = clang::ParmVarDecl::Create( + context, function_decl, clang::SourceLocation(), clang::SourceLocation(), + nullptr, void_ptr_type, nullptr, clang::SC_None, nullptr); + + function_decl->setParams({size_param, ptr_param}); + CARBON_CHECK(function_decl->isReservedGlobalPlacementOperator()); + return function_decl; +} + // Returns the GlobalDecl to use to represent the given function declaration. // TODO: Refactor with `Lower::CreateGlobalDecl`. static auto GetGlobalDecl(const clang::FunctionDecl* decl) @@ -487,7 +521,7 @@ static auto BuildCalleeArgs(clang::Sema& sema, // Builds the thunk function body which calls the callee function using the call // args and returns the callee function return value. Returns nullptr on // failure. -static auto BuildThunkBody(clang::Sema& sema, +static auto BuildThunkBody(CppContext& cpp_context, clang::Sema& sema, clang::FunctionDecl* thunk_function_decl, CalleeFunctionInfo callee_info) -> clang::StmtResult { @@ -566,10 +600,25 @@ static auto BuildThunkBody(clang::Sema& sema, auto return_type = callee_info.effective_return_type.getNonReferenceType(); 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()); + + auto* placement_new_decl = cpp_context.placement_new_decl(); + if (!placement_new_decl) { + placement_new_decl = GeneratePlacementNewFunctionDecl(sema.getASTContext()); + cpp_context.set_placement_new_decl(placement_new_decl); + } + sema.MarkFunctionReferenced(clang_loc, placement_new_decl); + clang::ImplicitAllocationParameters params(return_type, + clang::TypeAwareAllocationMode::No, + clang::AlignedAllocationMode::No); + clang::SourceRange range(clang_loc, clang_loc); + auto* placement_new = clang::CXXNewExpr::Create( + sema.getASTContext(), /*IsGlobalNew*/ true, placement_new_decl, + /*OperatorDelete*/ nullptr, params, /*UsualArrayDeleteWantsSize*/ false, + {return_object_addr}, + /*TypeIdParens=*/clang::SourceRange(), /*ArraySize=*/std::nullopt, + clang::CXXNewInitializationStyle::Parens, call.get(), + sema.getASTContext().getPointerType(return_type), return_type_info, range, + range); return sema.ActOnExprStmt(placement_new, /*DiscardedValue=*/true); } @@ -598,8 +647,8 @@ auto BuildCppThunk(Context& context, const SemIR::Function& callee_function) clang::Sema& sema = context.clang_sema(); clang::Sema::ContextRAII context_raii(sema, thunk_function_decl); sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl); - clang::StmtResult body = - BuildThunkBody(sema, thunk_function_decl, callee_info); + clang::StmtResult body = BuildThunkBody(*context.cpp_context(), sema, + thunk_function_decl, callee_info); sema.ActOnFinishFunctionBody(thunk_function_decl, body.get()); if (body.isInvalid()) { return nullptr; diff --git a/toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon b/toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon index 87203d57c147..7da3d5ffaa88 100644 --- a/toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon +++ b/toolchain/check/testdata/interop/cpp/function/thunk_ast.carbon @@ -18,55 +18,7 @@ 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: |-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]+}} <:176: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: `-FunctionDecl {{0x[a-f0-9]+}} 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' @@ -78,6 +30,7 @@ auto foo(short a) -> void; // CHECK:STDOUT: | `-DeclRefExpr {{0x[a-f0-9]+}} 'short * _Nonnull':'short *' lvalue ParmVar {{0x[a-f0-9]+}} 'a' 'short * _Nonnull':'short *' // CHECK:STDOUT: |-AlwaysInlineAttr {{0x[a-f0-9]+}} <> Implicit always_inline // CHECK:STDOUT: `-AsmLabelAttr {{0x[a-f0-9]+}} Implicit "_Z3foos.carbon_thunk" +// CHECK:STDOUT: TranslationUnitDecl {{0x[a-f0-9]+}} <> // --- import_thunk_required.carbon @@ -88,3 +41,27 @@ import Cpp library "thunk_required.h"; fn F() { Cpp.foo(1 as i16); } + +// --- return_thunk_required.h + +auto foo() -> short; +// CHECK:STDOUT: |-FunctionDecl {{0x[a-f0-9]+}} <./return_thunk_required.h:[[@LINE-1]]:1, col:15> col:6 used foo 'auto () -> short' +// CHECK:STDOUT: `-FunctionDecl {{0x[a-f0-9]+}} col:6 foo__carbon_thunk 'void (short * _Nonnull)' extern +// CHECK:STDOUT: |-ParmVarDecl {{0x[a-f0-9]+}} col:6 used return 'short * _Nonnull':'short *' +// CHECK:STDOUT: |-CXXNewExpr {{0x[a-f0-9]+}} 'short *' global Function {{0x[a-f0-9]+}} 'operator new' 'void *(__size_t, void *) noexcept' +// CHECK:STDOUT: | |-CallExpr {{0x[a-f0-9]+}} 'short' +// CHECK:STDOUT: | | `-ImplicitCastExpr {{0x[a-f0-9]+}} 'auto (*)() -> short' +// CHECK:STDOUT: | | `-DeclRefExpr {{0x[a-f0-9]+}} 'auto () -> short' Function {{0x[a-f0-9]+}} 'foo' 'auto () -> short' +// CHECK:STDOUT: | `-DeclRefExpr {{0x[a-f0-9]+}} 'short * _Nonnull':'short *' lvalue ParmVar {{0x[a-f0-9]+}} 'return' 'short * _Nonnull':'short *' +// CHECK:STDOUT: |-AlwaysInlineAttr {{0x[a-f0-9]+}} <> Implicit always_inline +// CHECK:STDOUT: `-AsmLabelAttr {{0x[a-f0-9]+}} Implicit "_Z3foov.carbon_thunk" + +// --- import_return_thunk_required.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "return_thunk_required.h"; + +fn F() -> i16 { + return Cpp.foo(); +} diff --git a/toolchain/check/testdata/interop/cpp/function/thunk_cpp26.carbon b/toolchain/check/testdata/interop/cpp/function/thunk_cpp26.carbon new file mode 100644 index 000000000000..dad0bd7b2c2f --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/function/thunk_cpp26.carbon @@ -0,0 +1,28 @@ +// 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/int.carbon +// EXTRA-ARGS: --clang-arg=--std=c++26 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/function/thunk_cpp26.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function/thunk_cpp26.carbon + +// --- thunk_required.h + +auto foo(short a) -> short; + +// --- import_thunk_required.carbon + +library "[[@TEST_NAME]]"; + +import Cpp library "thunk_required.h"; + +fn F() { + // Placement operator new is `constexpr` in C++26. But that shouldn't cause + // problems for us because we declare our own operator new. + Cpp.foo(1 as i16); +}