https://github.com/hekota created https://github.com/llvm/llvm-project/pull/191605
When aggregate types appear as _prvalues_ in HLSL initializer lists, convert them to _xvalues_ and wrap them in `OpaqueValueExpr` so their temporaries can be reused across all element accesses. This allows code generation to avoid emitting redundant copies of the same aggregate temporary. This should be especially helpful once support for constructors on user-defined structs is removed, and initializer lists will be the primary mechanism for struct initialization. A similar optimization may also be applicable to vector and matrix types. However, their current code generation path does not yet support handling `OpaqueValueExpr` in initializer lists. >From 9a9249637f54ad5c1d3aad538c30e13bc8a0e0e3 Mon Sep 17 00:00:00 2001 From: Helena Kotas <[email protected]> Date: Fri, 10 Apr 2026 22:31:00 -0700 Subject: [PATCH] [HLSL] Reuse temporaries of aggregate types in list initialization When aggregate types appear as prvalues in HLSL initializer lists, convert them to xvalues and wrap them in OpaqueExpr so their temporaries can be reused across all element accesses. This allows code generation to avoid emitting redundant copies of the same aggregate temporary. --- clang/lib/Sema/SemaHLSL.cpp | 17 ++++-- .../CodeGenHLSL/BasicFeatures/InitLists.hlsl | 53 ++----------------- clang/test/SemaHLSL/Language/InitListAST.hlsl | 10 ++++ 3 files changed, 27 insertions(+), 53 deletions(-) diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index a943303149931..d56bf1cfc43d7 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -5728,6 +5728,18 @@ class InitListTransformer { Ty->isHLSLAttributedResourceType()) return castInitializer(E); + // If this is an aggregate type and a prvalue, create an xvalue temporary + // so the member accesses will be xvalues. Wrap it in OpaqueExpr top make + // sure codegen will not generate duplicate copies. + if (E->isPRValue() && Ty->isAggregateType()) { + ExprResult TmpExpr = S.TemporaryMaterializationConversion(E); + if (TmpExpr.isInvalid()) + return false; + E = TmpExpr.get(); + E = new (Ctx) OpaqueValueExpr(E->getBeginLoc(), Ty, E->getValueKind(), + E->getObjectKind(), E); + } + if (auto *VecTy = Ty->getAs<VectorType>()) { uint64_t Size = VecTy->getNumElements(); @@ -5793,11 +5805,6 @@ class InitListTransformer { if (auto *RD = Ty->getAsCXXRecordDecl()) { llvm::SmallVector<CXXRecordDecl *> RecordDecls; RecordDecls.push_back(RD); - // If this is a prvalue create an xvalue so the member accesses - // will be xvalues. - if (E->isPRValue()) - E = new (Ctx) - MaterializeTemporaryExpr(Ty, E, /*BoundToLvalueReference=*/false); while (RecordDecls.back()->getNumBases()) { CXXRecordDecl *D = RecordDecls.back(); assert(D->getNumBases() == 1 && diff --git a/clang/test/CodeGenHLSL/BasicFeatures/InitLists.hlsl b/clang/test/CodeGenHLSL/BasicFeatures/InitLists.hlsl index 9c42da8962c2d..68050ddfe00d4 100644 --- a/clang/test/CodeGenHLSL/BasicFeatures/InitLists.hlsl +++ b/clang/test/CodeGenHLSL/BasicFeatures/InitLists.hlsl @@ -1155,9 +1155,6 @@ void case27(CustomResource a) { // CHECK-NEXT: [[TI:%.*]] = alloca [[STRUCT_TWOINTS:%.*]], align 1 // CHECK-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_TWOINTS]], align 1 // CHECK-NEXT: [[AGG_TEMP:%.*]] = alloca [[STRUCT_TWOFLOATS]], align 1 -// CHECK-NEXT: [[REF_TMP6:%.*]] = alloca [[STRUCT_TWOINTS]], align 1 -// CHECK-NEXT: [[AGG_TEMP7:%.*]] = alloca [[STRUCT_TWOFLOATS]], align 1 -// CHECK-NEXT: [[Z:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 0 // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[AGG_TEMP]], ptr align 1 [[TF]], i32 8, i1 false) // CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds [[STRUCT_TWOINTS]], ptr [[REF_TMP]], i32 0, i32 0 // CHECK-NEXT: [[GEP1:%.*]] = getelementptr inbounds [[STRUCT_TWOINTS]], ptr [[REF_TMP]], i32 0, i32 1 @@ -1169,22 +1166,12 @@ void case27(CustomResource a) { // CHECK-NEXT: [[TMP1:%.*]] = load float, ptr [[GEP3]], align 4 // CHECK-NEXT: [[CONV4:%.*]] = fptosi float [[TMP1]] to i32 // CHECK-NEXT: store i32 [[CONV4]], ptr [[GEP1]], align 4 +// CHECK-NEXT: [[Z:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 0 // CHECK-NEXT: [[Z5:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[REF_TMP]], i32 0, i32 0 // CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[Z5]], align 1 // CHECK-NEXT: store i32 [[TMP2]], ptr [[Z]], align 1 // CHECK-NEXT: [[W:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 1 -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[AGG_TEMP7]], ptr align 1 [[TF]], i32 8, i1 false) -// CHECK-NEXT: [[GEP8:%.*]] = getelementptr inbounds [[STRUCT_TWOINTS]], ptr [[REF_TMP6]], i32 0, i32 0 -// CHECK-NEXT: [[GEP9:%.*]] = getelementptr inbounds [[STRUCT_TWOINTS]], ptr [[REF_TMP6]], i32 0, i32 1 -// CHECK-NEXT: [[GEP10:%.*]] = getelementptr inbounds [[STRUCT_TWOFLOATS]], ptr [[AGG_TEMP7]], i32 0, i32 0 -// CHECK-NEXT: [[GEP11:%.*]] = getelementptr inbounds [[STRUCT_TWOFLOATS]], ptr [[AGG_TEMP7]], i32 0, i32 1 -// CHECK-NEXT: [[TMP3:%.*]] = load float, ptr [[GEP10]], align 4 -// CHECK-NEXT: [[CONV12:%.*]] = fptosi float [[TMP3]] to i32 -// CHECK-NEXT: store i32 [[CONV12]], ptr [[GEP8]], align 4 -// CHECK-NEXT: [[TMP4:%.*]] = load float, ptr [[GEP11]], align 4 -// CHECK-NEXT: [[CONV13:%.*]] = fptosi float [[TMP4]] to i32 -// CHECK-NEXT: store i32 [[CONV13]], ptr [[GEP9]], align 4 -// CHECK-NEXT: [[W14:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[REF_TMP6]], i32 0, i32 1 +// CHECK-NEXT: [[W14:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[REF_TMP]], i32 0, i32 1 // CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[W14]], align 1 // CHECK-NEXT: store i32 [[TMP5]], ptr [[W]], align 1 // CHECK-NEXT: ret void @@ -1199,8 +1186,6 @@ void case28(TwoFloats TF) { // CHECK-NEXT: [[INTS:%.*]] = alloca [2 x i32], align 4 // CHECK-NEXT: [[REF_TMP:%.*]] = alloca [2 x i32], align 4 // CHECK-NEXT: [[AGG_TEMP:%.*]] = alloca [[STRUCT_FOURFLOATS]], align 1 -// CHECK-NEXT: [[REF_TMP7:%.*]] = alloca [2 x i32], align 4 -// CHECK-NEXT: [[AGG_TEMP8:%.*]] = alloca [[STRUCT_FOURFLOATS]], align 1 // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[AGG_TEMP]], ptr align 1 [[FF]], i32 16, i1 false) // CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds [2 x i32], ptr [[REF_TMP]], i32 0, i32 0 // CHECK-NEXT: [[GEP1:%.*]] = getelementptr inbounds [2 x i32], ptr [[REF_TMP]], i32 0, i32 1 @@ -1218,20 +1203,7 @@ void case28(TwoFloats TF) { // CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[ARRAYIDX]], align 4 // CHECK-NEXT: store i32 [[TMP2]], ptr [[INTS]], align 4 // CHECK-NEXT: [[ARRAYINIT_ELEMENT:%.*]] = getelementptr inbounds i32, ptr [[INTS]], i32 1 -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[AGG_TEMP8]], ptr align 1 [[FF]], i32 16, i1 false) -// CHECK-NEXT: [[GEP9:%.*]] = getelementptr inbounds [2 x i32], ptr [[REF_TMP7]], i32 0, i32 0 -// CHECK-NEXT: [[GEP10:%.*]] = getelementptr inbounds [2 x i32], ptr [[REF_TMP7]], i32 0, i32 1 -// CHECK-NEXT: [[GEP11:%.*]] = getelementptr inbounds [[STRUCT_FOURFLOATS]], ptr [[AGG_TEMP8]], i32 0, i32 0, i32 0 -// CHECK-NEXT: [[GEP12:%.*]] = getelementptr inbounds [[STRUCT_FOURFLOATS]], ptr [[AGG_TEMP8]], i32 0, i32 0, i32 1 -// CHECK-NEXT: [[GEP13:%.*]] = getelementptr inbounds [[STRUCT_FOURFLOATS]], ptr [[AGG_TEMP8]], i32 0, i32 1 -// CHECK-NEXT: [[GEP14:%.*]] = getelementptr inbounds [[STRUCT_FOURFLOATS]], ptr [[AGG_TEMP8]], i32 0, i32 2 -// CHECK-NEXT: [[TMP3:%.*]] = load float, ptr [[GEP11]], align 4 -// CHECK-NEXT: [[CONV15:%.*]] = fptosi float [[TMP3]] to i32 -// CHECK-NEXT: store i32 [[CONV15]], ptr [[GEP9]], align 4 -// CHECK-NEXT: [[TMP4:%.*]] = load float, ptr [[GEP12]], align 4 -// CHECK-NEXT: [[CONV16:%.*]] = fptosi float [[TMP4]] to i32 -// CHECK-NEXT: store i32 [[CONV16]], ptr [[GEP10]], align 4 -// CHECK-NEXT: [[ARRAYIDX17:%.*]] = getelementptr inbounds nuw [2 x i32], ptr [[REF_TMP7]], i32 0, i32 1 +// CHECK-NEXT: [[ARRAYIDX17:%.*]] = getelementptr inbounds nuw [2 x i32], ptr [[REF_TMP]], i32 0, i32 1 // CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[ARRAYIDX17]], align 4 // CHECK-NEXT: store i32 [[TMP5]], ptr [[ARRAYINIT_ELEMENT]], align 4 // CHECK-NEXT: ret void @@ -1247,10 +1219,7 @@ void case29(FourFloats FF) { // CHECK-NEXT: [[TI:%.*]] = alloca [[STRUCT_TWOINTS:%.*]], align 1 // CHECK-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_TWOINTS]], align 1 // CHECK-NEXT: [[AGG_TEMP:%.*]] = alloca [4 x float], align 4 -// CHECK-NEXT: [[REF_TMP8:%.*]] = alloca [[STRUCT_TWOINTS]], align 1 -// CHECK-NEXT: [[AGG_TEMP9:%.*]] = alloca [4 x float], align 4 // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[ARR]], ptr align 4 @__const._Z6case30v.Arr, i32 16, i1 false) -// CHECK-NEXT: [[Z:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 0 // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[AGG_TEMP]], ptr align 4 [[ARR]], i32 16, i1 false) // CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds [[STRUCT_TWOINTS]], ptr [[REF_TMP]], i32 0, i32 0 // CHECK-NEXT: [[GEP1:%.*]] = getelementptr inbounds [[STRUCT_TWOINTS]], ptr [[REF_TMP]], i32 0, i32 1 @@ -1264,24 +1233,12 @@ void case29(FourFloats FF) { // CHECK-NEXT: [[TMP1:%.*]] = load float, ptr [[GEP3]], align 4 // CHECK-NEXT: [[CONV6:%.*]] = fptosi float [[TMP1]] to i32 // CHECK-NEXT: store i32 [[CONV6]], ptr [[GEP1]], align 4 +// CHECK-NEXT: [[Z:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 0 // CHECK-NEXT: [[Z7:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[REF_TMP]], i32 0, i32 0 // CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[Z7]], align 1 // CHECK-NEXT: store i32 [[TMP2]], ptr [[Z]], align 1 // CHECK-NEXT: [[W:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 1 -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[AGG_TEMP9]], ptr align 4 [[ARR]], i32 16, i1 false) -// CHECK-NEXT: [[GEP10:%.*]] = getelementptr inbounds [[STRUCT_TWOINTS]], ptr [[REF_TMP8]], i32 0, i32 0 -// CHECK-NEXT: [[GEP11:%.*]] = getelementptr inbounds [[STRUCT_TWOINTS]], ptr [[REF_TMP8]], i32 0, i32 1 -// CHECK-NEXT: [[GEP12:%.*]] = getelementptr inbounds [4 x float], ptr [[AGG_TEMP9]], i32 0, i32 0 -// CHECK-NEXT: [[GEP13:%.*]] = getelementptr inbounds [4 x float], ptr [[AGG_TEMP9]], i32 0, i32 1 -// CHECK-NEXT: [[GEP14:%.*]] = getelementptr inbounds [4 x float], ptr [[AGG_TEMP9]], i32 0, i32 2 -// CHECK-NEXT: [[GEP15:%.*]] = getelementptr inbounds [4 x float], ptr [[AGG_TEMP9]], i32 0, i32 3 -// CHECK-NEXT: [[TMP3:%.*]] = load float, ptr [[GEP12]], align 4 -// CHECK-NEXT: [[CONV16:%.*]] = fptosi float [[TMP3]] to i32 -// CHECK-NEXT: store i32 [[CONV16]], ptr [[GEP10]], align 4 -// CHECK-NEXT: [[TMP4:%.*]] = load float, ptr [[GEP13]], align 4 -// CHECK-NEXT: [[CONV17:%.*]] = fptosi float [[TMP4]] to i32 -// CHECK-NEXT: store i32 [[CONV17]], ptr [[GEP11]], align 4 -// CHECK-NEXT: [[W18:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[REF_TMP8]], i32 0, i32 1 +// CHECK-NEXT: [[W18:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[REF_TMP]], i32 0, i32 1 // CHECK-NEXT: [[TMP5:%.*]] = load i32, ptr [[W18]], align 1 // CHECK-NEXT: store i32 [[TMP5]], ptr [[W]], align 1 // CHECK-NEXT: ret void diff --git a/clang/test/SemaHLSL/Language/InitListAST.hlsl b/clang/test/SemaHLSL/Language/InitListAST.hlsl index 63e6af61b124a..62acaf3046548 100644 --- a/clang/test/SemaHLSL/Language/InitListAST.hlsl +++ b/clang/test/SemaHLSL/Language/InitListAST.hlsl @@ -1069,14 +1069,17 @@ float case17() { // CHECK-NEXT: FloatingLiteral {{.*}} 'float' 2.000000e+00 // CHECK-NEXT: DeclStmt // CHECK-NEXT: VarDecl {{.*}} used TI 'TwoInts' cinit +// CHECK-NEXT: ExprWithCleanups {{.*}} 'TwoInts' // CHECK-NEXT: InitListExpr {{.*}} 'TwoInts' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue> // CHECK-NEXT: MemberExpr {{.*}} 'int' xvalue .Z +// CHECK-NEXT: OpaqueValueExpr [[OPV0:0x[0-9a-f]+]] {{.*}} 'TwoInts' xvalue // CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'TwoInts' xvalue // CHECK-NEXT: CStyleCastExpr {{.*}} 'TwoInts' <HLSLElementwiseCast> // CHECK-NEXT: DeclRefExpr {{.*}} 'TwoFloats' lvalue Var {{.*}} 'TF' 'TwoFloats' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue> // CHECK-NEXT: MemberExpr {{.*}} 'int' xvalue .W +// CHECK-NEXT: OpaqueValueExpr [[OPV0]] {{.*}} 'TwoInts' xvalue // CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'TwoInts' xvalue // CHECK-NEXT: CStyleCastExpr {{.*}} 'TwoInts' <HLSLElementwiseCast> // CHECK-NEXT: DeclRefExpr {{.*}} 'TwoFloats' lvalue Var {{.*}} 'TF' 'TwoFloats' @@ -1101,6 +1104,7 @@ int case18() { // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue> // CHECK-NEXT: ArraySubscriptExpr {{.*}} 'int' xvalue // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int *' <ArrayToPointerDecay> +// CHECK-NEXT: OpaqueValueExpr [[OPV1:0x[0-9a-f]+]] {{.*}} 'int[4]' xvalue // CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'int[4]' xvalue // CHECK-NEXT: CStyleCastExpr {{.*}} 'int[4]' <HLSLElementwiseCast> // CHECK-NEXT: DeclRefExpr {{.*}} 'FourFloats' lvalue Var {{.*}} 'FF' 'FourFloats' @@ -1108,6 +1112,7 @@ int case18() { // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue> // CHECK-NEXT: ArraySubscriptExpr {{.*}} 'int' xvalue // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int *' <ArrayToPointerDecay> +// CHECK-NEXT: OpaqueValueExpr [[OPV1]] {{.*}} 'int[4]' xvalue // CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'int[4]' xvalue // CHECK-NEXT: CStyleCastExpr {{.*}} 'int[4]' <HLSLElementwiseCast> // CHECK-NEXT: DeclRefExpr {{.*}} 'FourFloats' lvalue Var {{.*}} 'FF' 'FourFloats' @@ -1115,6 +1120,7 @@ int case18() { // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue> // CHECK-NEXT: ArraySubscriptExpr {{.*}} 'int' xvalue // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int *' <ArrayToPointerDecay> +// CHECK-NEXT: OpaqueValueExpr [[OPV1]] {{.*}} 'int[4]' xvalue // CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'int[4]' xvalue // CHECK-NEXT: CStyleCastExpr {{.*}} 'int[4]' <HLSLElementwiseCast> // CHECK-NEXT: DeclRefExpr {{.*}} 'FourFloats' lvalue Var {{.*}} 'FF' 'FourFloats' @@ -1122,6 +1128,7 @@ int case18() { // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue> // CHECK-NEXT: ArraySubscriptExpr {{.*}} 'int' xvalue // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int *' <ArrayToPointerDecay> +// CHECK-NEXT: OpaqueValueExpr [[OPV1]] {{.*}} 'int[4]' xvalue // CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'int[4]' xvalue // CHECK-NEXT: CStyleCastExpr {{.*}} 'int[4]' <HLSLElementwiseCast> // CHECK-NEXT: DeclRefExpr {{.*}} 'FourFloats' lvalue Var {{.*}} 'FF' 'FourFloats' @@ -1141,15 +1148,18 @@ int case19() { // CHECK-NEXT: FloatingLiteral {{.*}} 'float' 4.000000e+00 // CHECK-NEXT: DeclStmt // CHECK-NEXT: VarDecl {{.*}} used TI 'TwoInts' cinit +// CHECK-NEXT: ExprWithCleanups {{.*}} 'TwoInts' // CHECK-NEXT: InitListExpr {{.*}} 'TwoInts' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue> // CHECK-NEXT: MemberExpr {{.*}} 'int' xvalue .Z +// CHECK-NEXT: OpaqueValueExpr [[OPV2:0x[0-9a-f]+]] {{.*}} 'TwoInts' xvalue // CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'TwoInts' xvalue // CHECK-NEXT: CStyleCastExpr {{.*}} 'TwoInts' <HLSLElementwiseCast> // CHECK-NEXT: ImplicitCastExpr {{.*}} 'float[4]' <HLSLArrayRValue> part_of_explicit_cast // CHECK-NEXT: DeclRefExpr {{.*}} 'float[4]' lvalue Var {{.*}} 'Arr' 'float[4]' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue> // CHECK-NEXT: MemberExpr {{.*}} 'int' xvalue .W +// CHECK-NEXT: OpaqueValueExpr [[OPV2]] {{.*}} 'TwoInts' xvalue // CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'TwoInts' xvalue // CHECK-NEXT: CStyleCastExpr {{.*}} 'TwoInts' <HLSLElementwiseCast> // CHECK-NEXT: ImplicitCastExpr {{.*}} 'float[4]' <HLSLArrayRValue> part_of_explicit_cast _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
