https://github.com/isoard-amd updated 
https://github.com/llvm/llvm-project/pull/191917

>From 32a5c751797a2749a0cdc5c3adc654631214f32f Mon Sep 17 00:00:00 2001
From: Alexandre Isoard <[email protected]>
Date: Tue, 14 Apr 2026 11:32:38 -0600
Subject: [PATCH 1/2] [utils] update_cc_test_checks.py: handle ExportDecl for
 HLSL and C++ modules

ExportDecl wraps functions declared with the HLSL `export` keyword and
C++20 `export` declarations. Add it to the set of container nodes that
the script recurses into, so that functions nested inside ExportDecl
get CHECK lines generated like any other function.

Assisted-By: Claude
---
 llvm/utils/update_cc_test_checks.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/llvm/utils/update_cc_test_checks.py 
b/llvm/utils/update_cc_test_checks.py
index 97b446d565973..c5a0ae37c0567 100755
--- a/llvm/utils/update_cc_test_checks.py
+++ b/llvm/utils/update_cc_test_checks.py
@@ -68,6 +68,7 @@ def parse_clang_ast_json(node, loc, search):
         if node_kind in (
             "NamespaceDecl",
             "LinkageSpecDecl",
+            "ExportDecl",
             "TranslationUnitDecl",
             "CXXRecordDecl",
             "ClassTemplateSpecializationDecl",

>From d588da54afbd787cb5fab543cbd50e83977c28ef Mon Sep 17 00:00:00 2001
From: Alexandre Isoard <[email protected]>
Date: Mon, 13 Apr 2026 17:38:39 -0600
Subject: [PATCH 2/2] [HLSL] Emit lifetime.start before copy-in for inout
 parameters

For inout parameters, Clang was emitting lifetime.start after the
copy-in store that initializes the temporary. Per LLVM's lifetime
semantics, any access to memory outside its lifetime is undefined
behavior, so the copy-in store was technically UB and the value was
undefined after lifetime.start.

Move EmitLifetimeStart into EmitHLSLOutArgLValues so that it is
emitted before EmitInitializationToLValue, putting the copy-in store
within the lifetime of the temporary.

Assisted-By: Claude
---
 clang/lib/CodeGen/CGExpr.cpp                  |  7 +-
 .../BasicFeatures/OutArgLifetime.hlsl         | 87 +++++++++++++++++++
 2 files changed, 92 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/CodeGenHLSL/BasicFeatures/OutArgLifetime.hlsl

diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 9107553652688..f9c98c132d9b4 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -6356,6 +6356,11 @@ CodeGenFunction::EmitHLSLOutArgLValues(const 
HLSLOutArgExpr *E, QualType Ty) {
   Address OutTemp = CreateIRTempWithoutCast(ExprTy);
   LValue TempLV = MakeAddrLValue(OutTemp, ExprTy);
 
+  // Start the lifetime before the copy-in so that the temporary is live when
+  // the initial value is written. This ensures the store is within the
+  // lifetime and is not killed by a store undef inserted at lifetime.start.
+  EmitLifetimeStart(OutTemp.getBasePointer());
+
   if (E->isInOut())
     EmitInitializationToLValue(E->getCastedTemporary()->getSourceExpr(),
                                TempLV);
@@ -6372,8 +6377,6 @@ LValue CodeGenFunction::EmitHLSLOutArgExpr(const 
HLSLOutArgExpr *E,
   llvm::Value *Addr = TempLV.getAddress().getBasePointer();
   llvm::Type *ElTy = ConvertTypeForMem(TempLV.getType());
 
-  EmitLifetimeStart(Addr);
-
   Address TmpAddr(Addr, ElTy, TempLV.getAlignment());
   Args.addWriteback(BaseLV, TmpAddr, nullptr, E->getWritebackCast());
   Args.add(RValue::get(TmpAddr, *this), Ty);
diff --git a/clang/test/CodeGenHLSL/BasicFeatures/OutArgLifetime.hlsl 
b/clang/test/CodeGenHLSL/BasicFeatures/OutArgLifetime.hlsl
new file mode 100644
index 0000000000000..4863d9d007279
--- /dev/null
+++ b/clang/test/CodeGenHLSL/BasicFeatures/OutArgLifetime.hlsl
@@ -0,0 +1,87 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --version 6
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -O1 
-disable-llvm-passes -emit-llvm -finclude-default-header -o - %s | FileCheck %s
+
+// Check that lifetime.start for an inout argument temporary is emitted
+// *before* the copy-in store, so that the store is within the lifetime
+// and is not treated as undefined behavior.
+
+// CHECK-LABEL: define hidden void @_Z9incrementRi(
+// CHECK-SAME: ptr noalias noundef nonnull align 4 dereferenceable(4) 
[[I:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[I_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    store ptr [[I]], ptr [[I_ADDR]], align 4, !tbaa 
[[INTPTR_TBAA6:![0-9]+]]
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[I_ADDR]], align 4, !tbaa 
[[INTPTR_TBAA6]], !nonnull [[META9:![0-9]+]], !align [[META10:![0-9]+]]
+// CHECK-NEXT:    [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4, !tbaa 
[[INT_TBAA2:![0-9]+]]
+// CHECK-NEXT:    [[ADD:%.*]] = add nsw i32 [[TMP1]], 1
+// CHECK-NEXT:    store i32 [[ADD]], ptr [[TMP0]], align 4, !tbaa [[INT_TBAA2]]
+// CHECK-NEXT:    ret void
+//
+void increment(inout int I) { I += 1; }
+
+// CHECK-LABEL: define hidden void @_Z5resetRi(
+// CHECK-SAME: ptr noalias noundef nonnull align 4 dereferenceable(4) 
[[I:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[I_ADDR:%.*]] = alloca ptr, align 4
+// CHECK-NEXT:    store ptr [[I]], ptr [[I_ADDR]], align 4, !tbaa 
[[INTPTR_TBAA6]]
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[I_ADDR]], align 4, !tbaa 
[[INTPTR_TBAA6]], !nonnull [[META9]], !align [[META10]]
+// CHECK-NEXT:    store i32 0, ptr [[TMP0]], align 4, !tbaa [[INT_TBAA2]]
+// CHECK-NEXT:    ret void
+//
+void reset(out int I) { I = 0; }
+
+// The lifetime.start must come before the copy-in load/store sequence.
+// CHECK-LABEL: define noundef i32 @_Z10inout_testi(
+// CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[X_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT:    [[TMP:%.*]] = alloca i32, align 4
+// CHECK-NEXT:    store i32 [[X]], ptr [[X_ADDR]], align 4, !tbaa [[INT_TBAA2]]
+// CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr [[TMP]]) 
#[[ATTR2:[0-9]+]]
+// CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4, !tbaa 
[[INT_TBAA2]]
+// CHECK-NEXT:    store i32 [[TMP0]], ptr [[TMP]], align 4, !tbaa [[INT_TBAA2]]
+// CHECK-NEXT:    call void @_Z9incrementRi(ptr noalias noundef nonnull align 
4 dereferenceable(4) [[TMP]]) #[[ATTR3:[0-9]+]]
+// CHECK-NEXT:    [[TMP1:%.*]] = load i32, ptr [[TMP]], align 4, !tbaa 
[[INT_TBAA2]]
+// CHECK-NEXT:    store i32 [[TMP1]], ptr [[X_ADDR]], align 4, !tbaa 
[[INT_TBAA2]]
+// CHECK-NEXT:    call void @llvm.lifetime.end.p0(ptr [[TMP]]) #[[ATTR2]]
+// CHECK-NEXT:    [[TMP2:%.*]] = load i32, ptr [[X_ADDR]], align 4, !tbaa 
[[INT_TBAA2]]
+// CHECK-NEXT:    ret i32 [[TMP2]]
+//
+export int inout_test(int X) {
+  increment(X);
+  return X;
+}
+
+// For `out` parameters there is no copy-in, so lifetime.start just needs
+// to appear before the call with no intervening store to the temporary.
+// CHECK-LABEL: define noundef i32 @_Z8out_testv(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[X:%.*]] = alloca i32, align 4
+// CHECK-NEXT:    [[TMP:%.*]] = alloca i32, align 4
+// CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr [[X]]) #[[ATTR2]]
+// CHECK-NEXT:    call void @llvm.lifetime.start.p0(ptr [[TMP]]) #[[ATTR2]]
+// CHECK-NEXT:    call void @_Z5resetRi(ptr noalias noundef nonnull align 4 
dereferenceable(4) [[TMP]]) #[[ATTR3]]
+// CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[TMP]], align 4, !tbaa 
[[INT_TBAA2]]
+// CHECK-NEXT:    store i32 [[TMP0]], ptr [[X]], align 4, !tbaa [[INT_TBAA2]]
+// CHECK-NEXT:    call void @llvm.lifetime.end.p0(ptr [[TMP]]) #[[ATTR2]]
+// CHECK-NEXT:    [[TMP1:%.*]] = load i32, ptr [[X]], align 4, !tbaa 
[[INT_TBAA2]]
+// CHECK-NEXT:    call void @llvm.lifetime.end.p0(ptr [[X]]) #[[ATTR2]]
+// CHECK-NEXT:    ret i32 [[TMP1]]
+//
+export int out_test() {
+  int X;
+  reset(X);
+  return X;
+}
+
+//.
+// CHECK: [[INT_TBAA2]] = !{[[META3:![0-9]+]], [[META3]], i64 0}
+// CHECK: [[META3]] = !{!"int", [[META4:![0-9]+]], i64 0}
+// CHECK: [[META4]] = !{!"omnipotent char", [[META5:![0-9]+]], i64 0}
+// CHECK: [[META5]] = !{!"Simple C++ TBAA"}
+// CHECK: [[INTPTR_TBAA6]] = !{[[META7:![0-9]+]], [[META7]], i64 0}
+// CHECK: [[META7]] = !{!"p1 int", [[META8:![0-9]+]], i64 0}
+// CHECK: [[META8]] = !{!"any pointer", [[META4]], i64 0}
+// CHECK: [[META9]] = !{}
+// CHECK: [[META10]] = !{i64 4}
+//.

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to