[clang] [WIP][clang]: Implement a conditional lifetimebound_if builtin. (PR #125520)

Haojian Wu via cfe-commits cfe-commits at lists.llvm.org
Mon Feb 3 07:46:44 PST 2025


https://github.com/hokein created https://github.com/llvm/llvm-project/pull/125520

This WIP PR explores the idea of introducing `[[clang::lifetimebound_if(<bool expression>)]]`,  a built-in attribute that conditionally applies `[[clang::lifetimebound]]` based  on the given boolean expression.  

One of the key challenges in adopting `[[clang::lifetimebound]]` and  `[[clang::lifetime_capture_by]]` is that these attributes should only apply to pointer-like types. Currently, we handle this by introducing multiple overloads, which increases code complexity and compilation overhead.  

This new attribute aims to simplify the implementation by enabling  conditional application, reducing the need for overloads.  

cc @usx95 @Xazax-hun @ilya-biryukov @higher-performance 

>From 4bf3dbe3d28e5ea5886f0a47ba4ff18a110e1c06 Mon Sep 17 00:00:00 2001
From: Haojian Wu <hokein.wu at gmail.com>
Date: Mon, 3 Feb 2025 16:32:26 +0100
Subject: [PATCH] [WIP][clang]: Implement a conditional lifetimebound_if
 builtin.

---
 clang/include/clang/Basic/Attr.td             |  8 +++++++
 clang/lib/Sema/CheckExprLifetime.cpp          | 21 +++++++++++++++++--
 clang/lib/Sema/SemaDeclAttr.cpp               |  7 +++++++
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  | 21 +++++++++++++++++++
 4 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 2a3a29bd2ee1cf..eb2a89487144c3 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3472,6 +3472,14 @@ def DiagnoseIf : InheritableAttr {
   let Documentation = [DiagnoseIfDocs];
 }
 
+def LifetimeBoundIf : Attr {
+  let Spellings = [Clang<"lifetimebound_if">];
+  let Subjects = SubjectList<[ParmVar, ImplicitObjectParameter], ErrorDiag>;
+  let Args = [ExprArgument<"Cond">];
+  // FIXME: add documentation
+  let Documentation = [InternalOnly];
+}
+
 def NoSpecializations : InheritableAttr {
   let Spellings = [Clang<"no_specializations", /*AllowInC*/0>];
   let Args = [StringArgument<"Message", 1>];
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index 8963cad86dbcae..fa9c850865a228 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -557,6 +557,23 @@ bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
   return isNormalAssignmentOperator(FD);
 }
 
+static bool hasLifetimeBoundAttribute(const Decl *D) {
+  if (D->hasAttr<LifetimeBoundAttr>())
+    return true;
+  if (const auto *AI = D->getAttr<LifetimeBoundIfAttr>()) {
+    bool Result;
+    // FIXME: we pay the cost of evaluating the binary condition everytime we
+    // check the existence of the attribute. Cache the result.
+    if (AI->getCond()->EvaluateAsBooleanCondition(Result, D->getASTContext())) {
+      if (Result)
+        return true;
+    } else {
+      // FIXME: emits an error.
+    }
+  }
+  return false;
+}
+
 // Visit lifetimebound or gsl-pointer arguments.
 static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
                                        LocalVisitor Visit) {
@@ -659,7 +676,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
       Arg = DAE->getExpr();
     }
     if (CheckCoroCall ||
-        CanonCallee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
+        hasLifetimeBoundAttribute(CanonCallee->getParamDecl(I)))
       VisitLifetimeBoundArg(CanonCallee->getParamDecl(I), Arg);
     else if (const auto *CaptureAttr =
                  CanonCallee->getParamDecl(I)->getAttr<LifetimeCaptureByAttr>();
@@ -1278,7 +1295,7 @@ static AnalysisResult analyzePathForGSLPointer(const IndirectLocalPath &Path,
 static bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD) {
   CMD = getDeclWithMergedLifetimeBoundAttrs(CMD);
   return CMD && isNormalAssignmentOperator(CMD) && CMD->param_size() == 1 &&
-         CMD->getParamDecl(0)->hasAttr<LifetimeBoundAttr>();
+         hasLifetimeBoundAttribute(CMD->getParamDecl(0));
 }
 
 static bool shouldRunGSLAssignmentAnalysis(const Sema &SemaRef,
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 9d7d22590bce4b..b290e729c87c38 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -3975,6 +3975,10 @@ static void handleLifetimeCaptureByAttr(Sema &S, Decl *D,
     D->addAttr(CaptureByAttr);
 }
 
+static void handleLifetimeBoundIfAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+  D->addAttr(LifetimeBoundIfAttr::Create(S.Context, AL.getArgAsExpr(0)));
+}
+
 void Sema::LazyProcessLifetimeCaptureByParams(FunctionDecl *FD) {
   bool HasImplicitThisParam = isInstanceMethod(FD);
   SmallVector<LifetimeCaptureByAttr *, 1> Attrs;
@@ -6823,6 +6827,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
   case ParsedAttr::AT_LifetimeCaptureBy:
     handleLifetimeCaptureByAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_LifetimeBoundIf:
+    handleLifetimeBoundIfAttr(S, D, AL);
+    break;
   case ParsedAttr::AT_CalledOnce:
     handleCalledOnceAttr(S, D, AL);
     break;
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 4855e8a23689ce..82b0cafac146f7 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -288,6 +288,22 @@ static void instantiateDependentDiagnoseIfAttr(
         DIA->getArgDependent(), New));
 }
 
+static void instantiateDependentLifetimeBoundIfAttr(
+    Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs,
+    const LifetimeBoundIfAttr *DIA, const Decl *Tmpl, Decl *New) {
+  const auto PVD = dyn_cast<ParmVarDecl>(New);
+  if (!PVD)
+    return;
+  auto *FD = dyn_cast<FunctionDecl>(PVD->getDeclContext());
+  if (!FD)
+    return;
+  Expr *Cond = instantiateDependentFunctionAttrCondition(
+      S, TemplateArgs, DIA, DIA->getCond(), Tmpl, FD);
+  if (Cond)
+    New->addAttr(new (S.getASTContext())
+                     LifetimeBoundIfAttr(S.getASTContext(), *DIA, Cond));
+}
+
 // Constructs and adds to New a new instance of CUDALaunchBoundsAttr using
 // template A as the base and arguments from TemplateArgs.
 static void instantiateDependentCUDALaunchBoundsAttr(
@@ -788,6 +804,11 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
                                          cast<FunctionDecl>(New));
       continue;
     }
+    if (const auto *LifetimeBoundIf = dyn_cast<LifetimeBoundIfAttr>(TmplAttr)) {
+      instantiateDependentLifetimeBoundIfAttr(*this, TemplateArgs,
+                                              LifetimeBoundIf, Tmpl, (New));
+      continue;
+    }
 
     if (const auto *CUDALaunchBounds =
             dyn_cast<CUDALaunchBoundsAttr>(TmplAttr)) {



More information about the cfe-commits mailing list