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
