Skip to content

Conversation

@HendrikHuebner
Copy link
Contributor

This PR adds __builtin_operator_new and __builtin_operator_delete. The implementation is taken from clang code gen.

@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Nov 18, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 18, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clangir

Author: Hendrik Hübner (HendrikHuebner)

Changes

This PR adds __builtin_operator_new and __builtin_operator_delete. The implementation is taken from clang code gen.


Full diff: https://github.com/llvm/llvm-project/pull/168578.diff

4 Files Affected:

  • (modified) clang/include/clang/CIR/MissingFeatures.h (+1)
  • (modified) clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp (+8)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp (+27)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+3)
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 567c79a27c07b..477d8046e18c0 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -200,6 +200,7 @@ struct MissingFeatures {
   static bool aggValueSlotMayOverlap() { return false; }
   static bool aggValueSlotVolatile() { return false; }
   static bool alignCXXRecordDecl() { return false; }
+  static bool allocToken() { return false; }
   static bool appleKext() { return false; }
   static bool armComputeVolatileBitfields() { return false; }
   static bool asmGoto() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 77f19343653db..2fbad2ecce0d3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -18,6 +18,7 @@
 #include "mlir/IR/BuiltinAttributes.h"
 #include "mlir/IR/Value.h"
 #include "mlir/Support/LLVM.h"
+#include "clang/AST/DeclBase.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/GlobalDecl.h"
 #include "clang/Basic/Builtins.h"
@@ -520,6 +521,13 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
     cir::PrefetchOp::create(builder, loc, address, locality, isWrite);
     return RValue::get(nullptr);
   }
+  case Builtin::BI__builtin_operator_new:
+    return emitNewOrDeleteBuiltinCall(
+        e->getCallee()->getType()->castAs<FunctionProtoType>(), e, false);
+  case Builtin::BI__builtin_operator_delete:
+    emitNewOrDeleteBuiltinCall(
+        e->getCallee()->getType()->castAs<FunctionProtoType>(), e, true);
+    return RValue::get(nullptr);
   }
 
   // If this is an alias for a lib function (e.g. __builtin_sin), emit
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 007d873ff5db6..f28887df34212 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -610,6 +610,33 @@ static RValue emitNewDeleteCall(CIRGenFunction &cgf,
   return rv;
 }
 
+RValue CIRGenFunction::emitNewOrDeleteBuiltinCall(const FunctionProtoType *type,
+                                                  const CallExpr *callExpr,
+                                                  bool isDelete) {
+  CallArgList args;
+  emitCallArgs(args, type, callExpr->arguments());
+  // Find the allocation or deallocation function that we're calling.
+  ASTContext &astContext = getContext();
+  DeclarationName name = astContext.DeclarationNames.getCXXOperatorName(
+      isDelete ? OO_Delete : OO_New);
+
+  clang::DeclContextLookupResult lookupResult =
+      astContext.getTranslationUnitDecl()->lookup(name);
+  for (const auto *decl : lookupResult) {
+    if (const auto *funcDecl = dyn_cast<FunctionDecl>(decl)) {
+      if (astContext.hasSameType(funcDecl->getType(), QualType(type, 0))) {
+        // Used for -fsanitize=alloc-token
+        assert(!cir::MissingFeatures::allocToken());
+
+        // Emit the call to operator new/delete.
+        return emitNewDeleteCall(*this, funcDecl, type, args);
+      }
+    }
+  }
+
+  llvm_unreachable("predeclared global operator new/delete is missing");
+}
+
 namespace {
 /// Calls the given 'operator delete' on a single object.
 struct CallObjectDelete final : EHScopeStack::Cleanup {
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 00f289bcd1bb2..0cfcc2be0255e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1476,6 +1476,9 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   RValue emitCXXPseudoDestructorExpr(const CXXPseudoDestructorExpr *expr);
 
+  RValue emitNewOrDeleteBuiltinCall(const FunctionProtoType *type,
+                                    const CallExpr *call, bool isDelete);
+
   void emitCXXTemporary(const CXXTemporary *temporary, QualType tempType,
                         Address ptr);
 

void test_builtins_basic() {
__builtin_operator_delete(__builtin_operator_new(4));
// CIR-LABEL: test_builtins_basic
// CIR: [[P:%.*]] = cir.call @_Znwm({{%.*}}) : (!u64i) -> !cir.ptr<!void>
Copy link
Contributor Author

@HendrikHuebner HendrikHuebner Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OGCG actually generates a bunch of attributes, something like: call noalias noundef nonnull ptr @_Znwm(i64 noundef 4).

Is it okay to ignore these with the wildcard syntax in the test?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are these set? And why is it not set in CIR pipeline?
I would prefer not to ignroe them in this case as CIRGen is creating the call in this case?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they're being set in CodeGenModule::ConstructAttributeList in classic codegen. This is a known hole in CIR codegen. Even if we were setting these the same as OGCG, I'd want to use a wildcard match here.

@github-actions
Copy link

github-actions bot commented Nov 18, 2025

🐧 Linux x64 Test Results

  • 112034 tests passed
  • 4077 tests skipped

Copy link
Contributor

@xlauko xlauko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm % nits

}
case Builtin::BI__builtin_operator_new:
return emitNewOrDeleteBuiltinCall(
e->getCallee()->getType()->castAs<FunctionProtoType>(), e, false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add comments /*ParamName=*/ for boolean parameters

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better still, you could replace the boolean parameter with OverloadedOperatorKind and pass OO_New or OO_Delete since the only thing the parameter is used for is to select between these two.

void test_builtins_basic() {
__builtin_operator_delete(__builtin_operator_new(4));
// CIR-LABEL: test_builtins_basic
// CIR: [[P:%.*]] = cir.call @_Znwm({{%.*}}) : (!u64i) -> !cir.ptr<!void>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are these set? And why is it not set in CIR pipeline?
I would prefer not to ignroe them in this case as CIRGen is creating the call in this case?

if (const auto *funcDecl = dyn_cast<FunctionDecl>(decl)) {
if (astContext.hasSameType(funcDecl->getType(), QualType(type, 0))) {
// Used for -fsanitize=alloc-token
assert(!cir::MissingFeatures::allocToken());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be wrapped in a check like:

          if (SanOpts.has(SanitizerKind::AllocToken)) {
            // Set !alloc_token metadata.
            assert(!cir::MissingFeatures::allocToken());
            errorNYI(...)
          }

If someone is using -fsanitize=alloc-token we should error, silent passing here could be super misleading. Note that some existing sanitizer checks we are not currently leading to errorNYI, so this isn't a strict rule necessarily, but it involving extra allocations and builtins justify the importance here IMO

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've been wildly inconsistent about that, but I agree that we should do better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants