[clang] [llvm] [BasicAA] Fix miscompilation with setjmp/longjmp due to missing longjmp re-entry paths in alias analysis (PR #212297)

via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 7 01:52:09 PDT 2026


https://github.com/midhuncodes7 updated https://github.com/llvm/llvm-project/pull/212297

>From 30ea53a4b734b9b6b128f4efd93eec2cc88fcbf6 Mon Sep 17 00:00:00 2001
From: Midhunesh <midhunesh.p at ibm.com>
Date: Thu, 16 Jul 2026 11:47:59 +0530
Subject: [PATCH 1/7] Implement ContainsReturnsTwiceCall attribute to handle
 returns_twice in Alias Analysis

---
 clang/lib/CodeGen/CGCall.cpp             |  4 ++++
 llvm/include/llvm/IR/Attributes.td       |  3 +++
 llvm/lib/Analysis/BasicAliasAnalysis.cpp | 11 ++++++++++-
 llvm/lib/IR/Attributes.cpp               |  5 +++++
 4 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 08cb9860f2f92..0919fb9171800 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6321,6 +6321,10 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
   CI->setAttributes(Attrs);
   CI->setCallingConv(static_cast<llvm::CallingConv::ID>(CallingConv));
 
+  // If this call can return twice (e.g. setjmp), mark the enclosing function
+  if (CI->hasFnAttr(llvm::Attribute::ReturnsTwice))
+    CurFn->addFnAttr(llvm::Attribute::ContainsReturnsTwiceCall);
+
   // Apply various metadata.
 
   if (!CI->getType()->isVoidTy())
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index 4e45100b54d38..6f8ea1d72143f 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -297,6 +297,9 @@ def ImmArg : EnumAttr<"immarg", IntersectPreserve, [ParamAttr]>;
 /// Function can return twice.
 def ReturnsTwice : EnumAttr<"returns_twice", IntersectPreserve, [FnAttr]>;
 
+/// Function contains a call to a function that can return twice (e.g. setjmp).
+def ContainsReturnsTwiceCall : EnumAttr<"contains-returns-twice-call", IntersectPreserve, [FnAttr]>;
+
 /// Safe Stack protection.
 def SafeStack : EnumAttr<"safestack", IntersectPreserve, [FnAttr]>;
 
diff --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
index 7df62577e04db..5b47d1a6782c1 100644
--- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
@@ -257,7 +257,16 @@ CaptureComponents EarliestEscapeAnalysis::getCapturesBefore(
       return isNotInCycle(I, &DT, LI, CI);
     }
 
-    return !isPotentiallyReachable(CaptureInst, I, nullptr, &DT, LI, CI);
+    if (isPotentiallyReachable(CaptureInst, I, nullptr, &DT, LI, CI))
+      return false;
+
+    // A `longjmp` may re-enter the function at any `returns_twice` call
+    // (e.g. `setjmp`), If the function contains such a call, conservatively
+    // treat the object as captured.
+    if (DT.getRoot()->getParent()->hasFnAttr(Attribute::ContainsReturnsTwiceCall))
+      return false;
+
+    return true;
   };
   if (IsNotCapturedBefore())
     return CaptureComponents::None;
diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp
index 4087b25951a1c..bf38b21cf573c 100644
--- a/llvm/lib/IR/Attributes.cpp
+++ b/llvm/lib/IR/Attributes.cpp
@@ -2793,6 +2793,11 @@ bool AttributeFuncs::areOutlineCompatible(const Function &A,
 void AttributeFuncs::mergeAttributesForInlining(Function &Caller,
                                                 const Function &Callee) {
   mergeFnAttrs(Caller, Callee);
+
+  // If the callee contained a returns_twice call (e.g. setjmp), those calls
+  // are now directly in the caller's body, so propagate the attribute.
+  if (Callee.hasFnAttribute(Attribute::ContainsReturnsTwiceCall))
+    Caller.addFnAttr(Attribute::ContainsReturnsTwiceCall);
 }
 
 void AttributeFuncs::mergeAttributesForOutlining(Function &Base,

>From e06312c1e5f93da08be7f9d004e1d799c6f155eb Mon Sep 17 00:00:00 2001
From: Midhunesh <midhunesh.p at ibm.com>
Date: Thu, 16 Jul 2026 12:36:18 +0530
Subject: [PATCH 2/7] Fix attribute

---
 llvm/include/llvm/IR/Attributes.td | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index 6f8ea1d72143f..072244c733412 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -298,7 +298,7 @@ def ImmArg : EnumAttr<"immarg", IntersectPreserve, [ParamAttr]>;
 def ReturnsTwice : EnumAttr<"returns_twice", IntersectPreserve, [FnAttr]>;
 
 /// Function contains a call to a function that can return twice (e.g. setjmp).
-def ContainsReturnsTwiceCall : EnumAttr<"contains-returns-twice-call", IntersectPreserve, [FnAttr]>;
+def ContainsReturnsTwiceCall : EnumAttr<"contains_returns_twice_call", IntersectPreserve, [FnAttr]>;
 
 /// Safe Stack protection.
 def SafeStack : EnumAttr<"safestack", IntersectPreserve, [FnAttr]>;

>From 99f20a33415a8ac8186982733e93de65fb8801aa Mon Sep 17 00:00:00 2001
From: Midhunesh <midhunesh.p at ibm.com>
Date: Thu, 16 Jul 2026 12:55:28 +0530
Subject: [PATCH 3/7] Fix attribute usage

---
 llvm/lib/Analysis/BasicAliasAnalysis.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
index 5b47d1a6782c1..95279f10d5b32 100644
--- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
@@ -263,7 +263,7 @@ CaptureComponents EarliestEscapeAnalysis::getCapturesBefore(
     // A `longjmp` may re-enter the function at any `returns_twice` call
     // (e.g. `setjmp`), If the function contains such a call, conservatively
     // treat the object as captured.
-    if (DT.getRoot()->getParent()->hasFnAttr(Attribute::ContainsReturnsTwiceCall))
+    if (DT.getRoot()->getParent()->hasFnAttribute(Attribute::ContainsReturnsTwiceCall))
       return false;
 
     return true;

>From 3afa336e6ea6be47533b1c9b6f1ec9df6fb6f58b Mon Sep 17 00:00:00 2001
From: Midhunesh <midhunesh.p at ibm.com>
Date: Mon, 27 Jul 2026 21:48:09 +0530
Subject: [PATCH 4/7] Add tests to check returns_twice

---
 .../setjmp-contains-returns-twice-attr.c      |  60 ++++++++++
 llvm/include/llvm/Bitcode/LLVMBitCodes.h      |   1 +
 llvm/lib/Bitcode/Reader/BitcodeReader.cpp     |   2 +
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp     |   2 +
 llvm/lib/Transforms/Utils/CodeExtractor.cpp   |   1 +
 llvm/test/Analysis/BasicAA/setjmp-longjmp.ll  | 104 ++++++++++++++++++
 6 files changed, 170 insertions(+)
 create mode 100644 clang/test/CodeGen/setjmp-contains-returns-twice-attr.c
 create mode 100644 llvm/test/Analysis/BasicAA/setjmp-longjmp.ll

diff --git a/clang/test/CodeGen/setjmp-contains-returns-twice-attr.c b/clang/test/CodeGen/setjmp-contains-returns-twice-attr.c
new file mode 100644
index 0000000000000..be3f5dffc75a6
--- /dev/null
+++ b/clang/test/CodeGen/setjmp-contains-returns-twice-attr.c
@@ -0,0 +1,60 @@
+// Test that functions containing setjmp / sigsetjmp / _setjmp calls, and
+// functions containing calls to user-declared __attribute__((returns_twice))
+// functions, are annotated with the contains_returns_twice_call attribute.
+//
+// The attribute is used by BasicAA to conservatively return MayAlias for
+// local allocas in such functions, preventing miscompilation via longjmp
+// re-entry paths invisible in the forward CFG.
+//
+// See: https://github.com/llvm/llvm-project/issues/198967
+
+// RUN: %clang_cc1 -x c %s -triple x86_64-linux-gnu -emit-llvm -o - | FileCheck %s
+
+struct __jmp_buf_tag { int n; };
+typedef struct __jmp_buf_tag jmp_buf[1];
+typedef struct __jmp_buf_tag sigjmp_buf[1];
+
+int setjmp(struct __jmp_buf_tag *);
+int sigsetjmp(struct __jmp_buf_tag *, int);
+int _setjmp(struct __jmp_buf_tag *);
+
+__attribute__((__returns_twice__)) void checkpoint(void);
+
+// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call
+// CHECK-NEXT: define{{.*}}@bar_setjmp(
+void bar_setjmp(void) {
+  jmp_buf buf;
+  setjmp(buf);
+}
+
+// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call
+// CHECK-NEXT: define{{.*}}@bar_sigsetjmp(
+void bar_sigsetjmp(void) {
+  sigjmp_buf buf;
+  sigsetjmp(buf, 0);
+}
+
+// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call
+// CHECK-NEXT: define{{.*}}@bar_setjmp_underscore(
+void bar_setjmp_underscore(void) {
+  jmp_buf buf;
+  _setjmp(buf);
+}
+
+// A user-declared returns_twice function must also trigger the attribute.
+// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call
+// CHECK-NEXT: define{{.*}}@bar_checkpoint(
+void bar_checkpoint(void) {
+  checkpoint();
+}
+
+// A function with no returns_twice call must NOT have the attribute.
+// The emitted "; Function Attrs:" line for bar_plain will contain only the
+// default function attributes (noinline nounwind optnone), not
+// contains_returns_twice_call.
+// CHECK: ; Function Attrs: noinline nounwind optnone
+// CHECK-NEXT: define{{.*}}@bar_plain(
+void bar_plain(void) {
+  int x = 42;
+  (void)x;
+}
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index 358f9a65a80af..ea365382aae76 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -826,6 +826,7 @@ enum AttributeKindCodes {
   ATTR_KIND_NOOUTLINE = 107,
   ATTR_KIND_FLATTEN = 108,
   ATTR_KIND_NOIPA = 109,
+  ATTR_KIND_CONTAINS_RETURNS_TWICE_CALL = 110,
 };
 
 enum ComdatSelectionKindCodes {
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index c57323ed41525..6cba4b6ae7fac 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -2334,6 +2334,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
     return Attribute::NoOutline;
   case bitc::ATTR_KIND_NOIPA:
     return Attribute::NoIPA;
+  case bitc::ATTR_KIND_CONTAINS_RETURNS_TWICE_CALL:
+    return Attribute::ContainsReturnsTwiceCall;
   }
 }
 
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index f4ce522c23b8e..005f744bb0b0e 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -1014,6 +1014,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
     return bitc::ATTR_KIND_NOOUTLINE;
   case Attribute::NoIPA:
     return bitc::ATTR_KIND_NOIPA;
+  case Attribute::ContainsReturnsTwiceCall:
+    return bitc::ATTR_KIND_CONTAINS_RETURNS_TWICE_CALL;
   case Attribute::EndAttrKinds:
     llvm_unreachable("Can not encode end-attribute kinds marker.");
   case Attribute::None:
diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
index 4c33848dacf51..bfd19b1cce779 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -954,6 +954,7 @@ Function *CodeExtractor::constructFunctionDeclaration(
       case Attribute::NoReturn:
       case Attribute::NoSync:
       case Attribute::ReturnsTwice:
+      case Attribute::ContainsReturnsTwiceCall:
       case Attribute::Speculatable:
       case Attribute::StackAlignment:
       case Attribute::WillReturn:
diff --git a/llvm/test/Analysis/BasicAA/setjmp-longjmp.ll b/llvm/test/Analysis/BasicAA/setjmp-longjmp.ll
new file mode 100644
index 0000000000000..63a73f2422d68
--- /dev/null
+++ b/llvm/test/Analysis/BasicAA/setjmp-longjmp.ll
@@ -0,0 +1,104 @@
+; Test that BasicAA conservatively returns MayAlias for local allocas in
+; functions containing returns_twice calls (e.g. setjmp/sigsetjmp), to prevent
+; miscompilation via longjmp re-entry paths invisible in the forward CFG.
+;
+; Reproduces: https://github.com/llvm/llvm-project/issues/198967
+;
+; Without the fix, GVN incorrectly concludes that the store through %p_val
+; (which may alias %i via a longjmp re-entry) cannot modify %i, then DSE
+; eliminates the "store i32 13, ptr %i" as dead. The fix adds an O(1)
+; check for the contains_returns_twice_call function attribute at the point
+; where isNotCapturedBefore would otherwise return true.
+
+; RUN: opt < %s -aa-pipeline=basic-aa -passes=gvn,dse -S | FileCheck %s
+
+target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at x  = external global i32
+ at ii = external global i32
+ at p  = external global ptr
+
+declare void @redo()
+declare void @checkpoint() #0
+
+; Control case: function has the same CFG structure but no contains_returns_twice_call
+; attribute. GVN/DSE can legally eliminate "store i32 13, ptr %i" because in the
+; forward CFG the only path to if.else does not pass through if.then (where &i is
+; stored to @p). BasicAA correctly returns NoAlias for (%i, %p_val) here.
+;
+; Fix case CHECK directives are listed here so that CHECK-NOT is correctly scoped
+; between the two CHECK-LABELs: it checks that the store is absent in bar_no_attr
+; and present in bar_with_attr.
+;
+; CHECK-LABEL: define void @bar_no_attr(
+; CHECK-NOT:   store i32 13, ptr %i
+; CHECK-LABEL: define void @bar_with_attr(
+; CHECK:       store i32 13, ptr %i
+define void @bar_no_attr() {
+entry:
+  %i = alloca i32, align 4
+  %x_val = load i32, ptr @x
+  %cond = icmp ne i32 %x_val, 0
+  br i1 %cond, label %if.then, label %if.else
+
+if.then:
+  store ptr %i, ptr @p
+  call void @redo()
+  br label %if.end
+
+if.else:
+  ; Bug: without the attribute, GVN sees "store i32 42, ptr %p_val" as
+  ; NoAlias with %i (the capture in if.then is not visible in the forward
+  ; CFG), so it forwards 13 through the load and DSE removes this store.
+  store i32 13, ptr %i
+  %p_val = load ptr, ptr @p
+  store i32 42, ptr %p_val
+  %ii_val = load i32, ptr %i
+  store i32 %ii_val, ptr @ii
+  br label %if.end
+
+if.end:
+  ret void
+}
+
+; Fix case: same structure with contains_returns_twice_call on the function
+; (set automatically by Clang when emitting setjmp / sigsetjmp / any
+; returns_twice call). BasicAA conservatively returns MayAlias for (%i,
+; %p_val) because a longjmp can re-enter at the checkpoint() site with @p
+; already holding &i, making the if.then capture visible to if.else.
+;
+; The "store i32 13, ptr %i" MUST be preserved; removing it is a
+; miscompilation. (CHECKs for this function are listed above to correctly
+; scope the CHECK-NOT between the two CHECK-LABELs.)
+define void @bar_with_attr() #1 {
+entry:
+  %i = alloca i32, align 4
+  call void @checkpoint() #0
+  %x_val = load i32, ptr @x
+  %cond = icmp ne i32 %x_val, 0
+  br i1 %cond, label %if.then, label %if.else
+
+if.then:
+  ; On the first pass: stores &i into @p, then calls redo().
+  ; redo() performs longjmp(buf) which re-enters at checkpoint() above.
+  ; On re-entry: if.then runs again setting p = &i, then if.else runs
+  ; with @p == &i, making "store i32 42, ptr %p_val" alias "store i32 13, ptr %i".
+  store ptr %i, ptr @p
+  call void @redo()
+  br label %if.end
+
+if.else:
+  store i32 13, ptr %i
+  %p_val = load ptr, ptr @p
+  store i32 42, ptr %p_val   ; may write to %i via longjmp re-entry path
+  %ii_val = load i32, ptr %i
+  store i32 %ii_val, ptr @ii
+  br label %if.end
+
+if.end:
+  ret void
+}
+
+attributes #0 = { returns_twice }
+attributes #1 = { contains_returns_twice_call }

>From be00255c1d0675e9e17130bc5117230821931801 Mon Sep 17 00:00:00 2001
From: Midhunesh <midhuensh.p at ibm.com>
Date: Tue, 28 Jul 2026 00:16:38 -0400
Subject: [PATCH 5/7] Fix formatting

---
 llvm/lib/Analysis/BasicAliasAnalysis.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
index 95279f10d5b32..f488a576e72ce 100644
--- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
@@ -263,7 +263,8 @@ CaptureComponents EarliestEscapeAnalysis::getCapturesBefore(
     // A `longjmp` may re-enter the function at any `returns_twice` call
     // (e.g. `setjmp`), If the function contains such a call, conservatively
     // treat the object as captured.
-    if (DT.getRoot()->getParent()->hasFnAttribute(Attribute::ContainsReturnsTwiceCall))
+    if (DT.getRoot()->getParent()->hasFnAttribute(
+            Attribute::ContainsReturnsTwiceCall))
       return false;
 
     return true;

>From a57fc98095fbbf8cb2c12e2b15e17c2d3835dc91 Mon Sep 17 00:00:00 2001
From: Midhunesh <midhunesh.p at ibm.com>
Date: Tue, 28 Jul 2026 11:13:47 +0530
Subject: [PATCH 6/7] Fix affected test

---
 clang/lib/CodeGen/CGCall.cpp             | 2 +-
 clang/test/CodeGen/function-attributes.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index cf78da81cece6..903eedd8f89b7 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6375,7 +6375,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
   CI->setAttributes(Attrs);
   CI->setCallingConv(static_cast<llvm::CallingConv::ID>(CallingConv));
 
-  // If this call can return twice (e.g. setjmp), mark the enclosing function
+  // If this call can return twice (e.g. setjmp), mark the enclosing function.
   if (CI->hasFnAttr(llvm::Attribute::ReturnsTwice))
     CurFn->addFnAttr(llvm::Attribute::ContainsReturnsTwiceCall);
 
diff --git a/clang/test/CodeGen/function-attributes.c b/clang/test/CodeGen/function-attributes.c
index bb8670a8530e0..1bef07fa7e099 100644
--- a/clang/test/CodeGen/function-attributes.c
+++ b/clang/test/CodeGen/function-attributes.c
@@ -113,7 +113,7 @@ void f20(void) {
 // CHECK: attributes [[AI]] = { alwaysinline nounwind optsize{{.*}} }
 // CHECK: attributes [[NUW_OS_RN]] = { nounwind optsize willreturn memory(none){{.*}} }
 // CHECK: attributes [[SR]] = { nounwind optsize{{.*}} "stackrealign"{{.*}} }
-// CHECK: attributes [[RT]] = { nounwind optsize returns_twice{{.*}} }
+// CHECK: attributes [[RT]] = { contains_returns_twice_call nounwind optsize returns_twice{{.*}} }
 // CHECK: attributes [[NR]] = { noreturn optsize }
 // CHECK: attributes [[NUW_RN]] = { nounwind optsize willreturn memory(none) }
 // CHECK: attributes [[RT_CALL]] = { optsize returns_twice }

>From be5cf7ad70205c87a4cc65eba40fe22856eec2d2 Mon Sep 17 00:00:00 2001
From: Midhunesh <midhunesh.p at ibm.com>
Date: Mon, 7 Sep 2026 14:21:39 +0530
Subject: [PATCH 7/7] Cache setjmp/longjmp check in EarliestEscapeAnalysis
 instead of a new attribute

---
 clang/lib/CodeGen/CGCall.cpp                  |  4 --
 clang/test/CodeGen/function-attributes.c      |  2 +-
 .../setjmp-contains-returns-twice-attr.c      | 60 -------------------
 llvm/include/llvm/Analysis/AliasAnalysis.h    |  8 +++
 llvm/include/llvm/IR/Attributes.td            |  3 -
 llvm/lib/Analysis/BasicAliasAnalysis.cpp      | 10 +++-
 llvm/lib/Bitcode/Reader/BitcodeReader.cpp     |  2 -
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp     |  2 -
 llvm/lib/IR/Attributes.cpp                    |  5 --
 llvm/lib/Transforms/Utils/CodeExtractor.cpp   |  1 -
 llvm/test/Analysis/BasicAA/setjmp-longjmp.ll  | 43 ++++++-------
 11 files changed, 39 insertions(+), 101 deletions(-)
 delete mode 100644 clang/test/CodeGen/setjmp-contains-returns-twice-attr.c

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 0c43b0e2cf2fa..1221829871b9f 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6477,10 +6477,6 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
   CI->setAttributes(Attrs);
   CI->setCallingConv(static_cast<llvm::CallingConv::ID>(CallingConv));
 
-  // If this call can return twice (e.g. setjmp), mark the enclosing function.
-  if (CI->hasFnAttr(llvm::Attribute::ReturnsTwice))
-    CurFn->addFnAttr(llvm::Attribute::ContainsReturnsTwiceCall);
-
   // Apply various metadata.
 
   if (!CI->getType()->isVoidTy())
diff --git a/clang/test/CodeGen/function-attributes.c b/clang/test/CodeGen/function-attributes.c
index 1bef07fa7e099..bb8670a8530e0 100644
--- a/clang/test/CodeGen/function-attributes.c
+++ b/clang/test/CodeGen/function-attributes.c
@@ -113,7 +113,7 @@ void f20(void) {
 // CHECK: attributes [[AI]] = { alwaysinline nounwind optsize{{.*}} }
 // CHECK: attributes [[NUW_OS_RN]] = { nounwind optsize willreturn memory(none){{.*}} }
 // CHECK: attributes [[SR]] = { nounwind optsize{{.*}} "stackrealign"{{.*}} }
-// CHECK: attributes [[RT]] = { contains_returns_twice_call nounwind optsize returns_twice{{.*}} }
+// CHECK: attributes [[RT]] = { nounwind optsize returns_twice{{.*}} }
 // CHECK: attributes [[NR]] = { noreturn optsize }
 // CHECK: attributes [[NUW_RN]] = { nounwind optsize willreturn memory(none) }
 // CHECK: attributes [[RT_CALL]] = { optsize returns_twice }
diff --git a/clang/test/CodeGen/setjmp-contains-returns-twice-attr.c b/clang/test/CodeGen/setjmp-contains-returns-twice-attr.c
deleted file mode 100644
index be3f5dffc75a6..0000000000000
--- a/clang/test/CodeGen/setjmp-contains-returns-twice-attr.c
+++ /dev/null
@@ -1,60 +0,0 @@
-// Test that functions containing setjmp / sigsetjmp / _setjmp calls, and
-// functions containing calls to user-declared __attribute__((returns_twice))
-// functions, are annotated with the contains_returns_twice_call attribute.
-//
-// The attribute is used by BasicAA to conservatively return MayAlias for
-// local allocas in such functions, preventing miscompilation via longjmp
-// re-entry paths invisible in the forward CFG.
-//
-// See: https://github.com/llvm/llvm-project/issues/198967
-
-// RUN: %clang_cc1 -x c %s -triple x86_64-linux-gnu -emit-llvm -o - | FileCheck %s
-
-struct __jmp_buf_tag { int n; };
-typedef struct __jmp_buf_tag jmp_buf[1];
-typedef struct __jmp_buf_tag sigjmp_buf[1];
-
-int setjmp(struct __jmp_buf_tag *);
-int sigsetjmp(struct __jmp_buf_tag *, int);
-int _setjmp(struct __jmp_buf_tag *);
-
-__attribute__((__returns_twice__)) void checkpoint(void);
-
-// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call
-// CHECK-NEXT: define{{.*}}@bar_setjmp(
-void bar_setjmp(void) {
-  jmp_buf buf;
-  setjmp(buf);
-}
-
-// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call
-// CHECK-NEXT: define{{.*}}@bar_sigsetjmp(
-void bar_sigsetjmp(void) {
-  sigjmp_buf buf;
-  sigsetjmp(buf, 0);
-}
-
-// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call
-// CHECK-NEXT: define{{.*}}@bar_setjmp_underscore(
-void bar_setjmp_underscore(void) {
-  jmp_buf buf;
-  _setjmp(buf);
-}
-
-// A user-declared returns_twice function must also trigger the attribute.
-// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call
-// CHECK-NEXT: define{{.*}}@bar_checkpoint(
-void bar_checkpoint(void) {
-  checkpoint();
-}
-
-// A function with no returns_twice call must NOT have the attribute.
-// The emitted "; Function Attrs:" line for bar_plain will contain only the
-// default function attributes (noinline nounwind optnone), not
-// contains_returns_twice_call.
-// CHECK: ; Function Attrs: noinline nounwind optnone
-// CHECK-NEXT: define{{.*}}@bar_plain(
-void bar_plain(void) {
-  int x = 42;
-  (void)x;
-}
diff --git a/llvm/include/llvm/Analysis/AliasAnalysis.h b/llvm/include/llvm/Analysis/AliasAnalysis.h
index 9266f3c3a63a0..e74e782e84126 100644
--- a/llvm/include/llvm/Analysis/AliasAnalysis.h
+++ b/llvm/include/llvm/Analysis/AliasAnalysis.h
@@ -192,6 +192,14 @@ class LLVM_ABI EarliestEscapeAnalysis final : public CaptureAnalysis {
   /// This is used for cache invalidation purposes.
   DenseMap<Instruction *, TinyPtrVector<const Value *>> Inst2Obj;
 
+  /// Whether the function contains a call to a function that may return
+  /// twice (e.g. setjmp). Lazily computed and cached, since a `longjmp` may
+  /// re-enter the function at any such call, which this analysis needs to
+  /// account for conservatively.
+  std::optional<bool> ContainsReturnsTwiceCall;
+
+  bool containsReturnsTwiceCall();
+
 public:
   EarliestEscapeAnalysis(DominatorTree &DT, const LoopInfo *LI = nullptr,
                          const CycleInfo *CI = nullptr)
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index fafb0d995ab18..ea523cad69cd5 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -301,9 +301,6 @@ def ImmArg : EnumAttr<"immarg", IntersectPreserve, [ParamAttr]>;
 /// Function can return twice.
 def ReturnsTwice : EnumAttr<"returns_twice", IntersectPreserve, [FnAttr]>;
 
-/// Function contains a call to a function that can return twice (e.g. setjmp).
-def ContainsReturnsTwiceCall : EnumAttr<"contains_returns_twice_call", IntersectPreserve, [FnAttr]>;
-
 /// Safe Stack protection.
 def SafeStack : EnumAttr<"safestack", IntersectPreserve, [FnAttr]>;
 
diff --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
index 9e33825094cf6..fba86771b644d 100644
--- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
@@ -263,8 +263,7 @@ CaptureComponents EarliestEscapeAnalysis::getCapturesBefore(
     // A `longjmp` may re-enter the function at any `returns_twice` call
     // (e.g. `setjmp`), If the function contains such a call, conservatively
     // treat the object as captured.
-    if (DT.getRoot()->getParent()->hasFnAttribute(
-            Attribute::ContainsReturnsTwiceCall))
+    if (containsReturnsTwiceCall())
       return false;
 
     return true;
@@ -274,6 +273,13 @@ CaptureComponents EarliestEscapeAnalysis::getCapturesBefore(
   return Iter.first->second.second.WithoutRet;
 }
 
+bool EarliestEscapeAnalysis::containsReturnsTwiceCall() {
+  if (!ContainsReturnsTwiceCall)
+    ContainsReturnsTwiceCall =
+        DT.getRoot()->getParent()->callsFunctionThatReturnsTwice();
+  return *ContainsReturnsTwiceCall;
+}
+
 void EarliestEscapeAnalysis::removeInstruction(Instruction *I) {
   auto Iter = Inst2Obj.find(I);
   if (Iter != Inst2Obj.end()) {
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index 0a6a362e0036f..35e71c93c0109 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -2338,8 +2338,6 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
     return Attribute::NoOutline;
   case bitc::ATTR_KIND_NOIPA:
     return Attribute::NoIPA;
-  case bitc::ATTR_KIND_CONTAINS_RETURNS_TWICE_CALL:
-    return Attribute::ContainsReturnsTwiceCall;
   }
 }
 
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index de26a44269753..859e073b91cc5 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -1018,8 +1018,6 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
     return bitc::ATTR_KIND_NOOUTLINE;
   case Attribute::NoIPA:
     return bitc::ATTR_KIND_NOIPA;
-  case Attribute::ContainsReturnsTwiceCall:
-    return bitc::ATTR_KIND_CONTAINS_RETURNS_TWICE_CALL;
   case Attribute::EndAttrKinds:
     llvm_unreachable("Can not encode end-attribute kinds marker.");
   case Attribute::None:
diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp
index 9ad6f6a5842d0..c75f0536f172d 100644
--- a/llvm/lib/IR/Attributes.cpp
+++ b/llvm/lib/IR/Attributes.cpp
@@ -2770,11 +2770,6 @@ bool AttributeFuncs::areOutlineCompatible(const Function &A,
 void AttributeFuncs::mergeAttributesForInlining(Function &Caller,
                                                 const Function &Callee) {
   mergeFnAttrs(Caller, Callee);
-
-  // If the callee contained a returns_twice call (e.g. setjmp), those calls
-  // are now directly in the caller's body, so propagate the attribute.
-  if (Callee.hasFnAttribute(Attribute::ContainsReturnsTwiceCall))
-    Caller.addFnAttr(Attribute::ContainsReturnsTwiceCall);
 }
 
 void AttributeFuncs::mergeAttributesForOutlining(Function &Base,
diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
index 3e94101ccaecf..4f224d0a18e48 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -960,7 +960,6 @@ Function *CodeExtractor::constructFunctionDeclaration(
       case Attribute::NoReturn:
       case Attribute::NoSync:
       case Attribute::ReturnsTwice:
-      case Attribute::ContainsReturnsTwiceCall:
       case Attribute::Speculatable:
       case Attribute::StackAlignment:
       case Attribute::WillReturn:
diff --git a/llvm/test/Analysis/BasicAA/setjmp-longjmp.ll b/llvm/test/Analysis/BasicAA/setjmp-longjmp.ll
index 63a73f2422d68..90f3d6412c5b0 100644
--- a/llvm/test/Analysis/BasicAA/setjmp-longjmp.ll
+++ b/llvm/test/Analysis/BasicAA/setjmp-longjmp.ll
@@ -6,9 +6,10 @@
 ;
 ; Without the fix, GVN incorrectly concludes that the store through %p_val
 ; (which may alias %i via a longjmp re-entry) cannot modify %i, then DSE
-; eliminates the "store i32 13, ptr %i" as dead. The fix adds an O(1)
-; check for the contains_returns_twice_call function attribute at the point
-; where isNotCapturedBefore would otherwise return true.
+; eliminates the "store i32 13, ptr %i" as dead. The fix makes
+; EarliestEscapeAnalysis conservatively bail out at the point where
+; isNotCapturedBefore would otherwise return true, if the function contains
+; any returns_twice call.
 
 ; RUN: opt < %s -aa-pipeline=basic-aa -passes=gvn,dse -S | FileCheck %s
 
@@ -22,20 +23,21 @@ target triple = "x86_64-unknown-linux-gnu"
 declare void @redo()
 declare void @checkpoint() #0
 
-; Control case: function has the same CFG structure but no contains_returns_twice_call
-; attribute. GVN/DSE can legally eliminate "store i32 13, ptr %i" because in the
-; forward CFG the only path to if.else does not pass through if.then (where &i is
-; stored to @p). BasicAA correctly returns NoAlias for (%i, %p_val) here.
+; Control case: function has the same CFG structure but calls no
+; returns_twice function. GVN/DSE can legally eliminate "store i32 13, ptr %i"
+; because in the forward CFG the only path to if.else does not pass through
+; if.then (where &i is stored to @p). BasicAA correctly returns NoAlias for
+; (%i, %p_val) here.
 ;
-; Fix case CHECK directives are listed here so that CHECK-NOT is correctly scoped
-; between the two CHECK-LABELs: it checks that the store is absent in bar_no_attr
-; and present in bar_with_attr.
+; Fix case CHECK directives are listed here so that CHECK-NOT is correctly
+; scoped between the two CHECK-LABELs: it checks that the store is absent in
+; bar_no_returns_twice and present in bar_with_returns_twice.
 ;
-; CHECK-LABEL: define void @bar_no_attr(
+; CHECK-LABEL: define void @bar_no_returns_twice(
 ; CHECK-NOT:   store i32 13, ptr %i
-; CHECK-LABEL: define void @bar_with_attr(
+; CHECK-LABEL: define void @bar_with_returns_twice(
 ; CHECK:       store i32 13, ptr %i
-define void @bar_no_attr() {
+define void @bar_no_returns_twice() {
 entry:
   %i = alloca i32, align 4
   %x_val = load i32, ptr @x
@@ -48,7 +50,7 @@ if.then:
   br label %if.end
 
 if.else:
-  ; Bug: without the attribute, GVN sees "store i32 42, ptr %p_val" as
+  ; Bug: without the fix, GVN sees "store i32 42, ptr %p_val" as
   ; NoAlias with %i (the capture in if.then is not visible in the forward
   ; CFG), so it forwards 13 through the load and DSE removes this store.
   store i32 13, ptr %i
@@ -62,16 +64,16 @@ if.end:
   ret void
 }
 
-; Fix case: same structure with contains_returns_twice_call on the function
-; (set automatically by Clang when emitting setjmp / sigsetjmp / any
-; returns_twice call). BasicAA conservatively returns MayAlias for (%i,
-; %p_val) because a longjmp can re-enter at the checkpoint() site with @p
-; already holding &i, making the if.then capture visible to if.else.
+; Fix case: same structure, but the function calls @checkpoint(), which is
+; returns_twice (as Clang emits for setjmp / sigsetjmp / any returns_twice
+; call). BasicAA conservatively returns MayAlias for (%i, %p_val) because a
+; longjmp can re-enter at the checkpoint() site with @p already holding &i,
+; making the if.then capture visible to if.else.
 ;
 ; The "store i32 13, ptr %i" MUST be preserved; removing it is a
 ; miscompilation. (CHECKs for this function are listed above to correctly
 ; scope the CHECK-NOT between the two CHECK-LABELs.)
-define void @bar_with_attr() #1 {
+define void @bar_with_returns_twice() {
 entry:
   %i = alloca i32, align 4
   call void @checkpoint() #0
@@ -101,4 +103,3 @@ if.end:
 }
 
 attributes #0 = { returns_twice }
-attributes #1 = { contains_returns_twice_call }



More information about the cfe-commits mailing list