From 3f2a683f249449754af90404b7eedb7622cc750c Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Tue, 30 Sep 2025 13:43:33 -0500 Subject: [PATCH 1/4] [HLSL] Fix vector list initialization This simplifies and cleans up the vector list initialization behavior. This simplifies the work we do in SemaInit by just relying on SemaHLSL's initialization list flattening. This change fixes some outstanding limitations by supporting structure to vector initialization, but re-introduces HLSL's limitations around overload resolution in initializers. --- clang/include/clang/Sema/Initialization.h | 3 + clang/lib/Sema/SemaInit.cpp | 45 +++-- clang/test/AST/HLSL/vector-constructors.hlsl | 23 +-- .../CodeGenHLSL/BasicFeatures/InitLists.hlsl | 173 +++++++++++++----- .../BuiltIns/vector-constructors-erros.hlsl | 23 ++- 5 files changed, 168 insertions(+), 99 deletions(-) diff --git a/clang/include/clang/Sema/Initialization.h b/clang/include/clang/Sema/Initialization.h index d7675ea153afb..5e96317ffb7fe 100644 --- a/clang/include/clang/Sema/Initialization.h +++ b/clang/include/clang/Sema/Initialization.h @@ -1126,6 +1126,9 @@ class InitializationSequence { // A designated initializer was provided for a non-aggregate type. FK_DesignatedInitForNonAggregate, + + /// HLSL intialization list flattening failed. + FK_HLSLInitListFlatteningFailed, }; private: diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index c97129336736b..6777f98737e3c 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -3920,6 +3920,7 @@ bool InitializationSequence::isAmbiguous() const { case FK_AddressOfUnaddressableFunction: case FK_ParenthesizedListInitFailed: case FK_DesignatedInitForNonAggregate: + case FK_HLSLInitListFlatteningFailed: return false; case FK_ReferenceInitOverloadFailed: @@ -4882,8 +4883,10 @@ static void TryListInitialization(Sema &S, bool TreatUnavailableAsInvalid) { QualType DestType = Entity.getType(); - if (S.getLangOpts().HLSL && !S.HLSL().transformInitList(Entity, InitList)) + if (S.getLangOpts().HLSL && !S.HLSL().transformInitList(Entity, InitList)) { + Sequence.SetFailed(InitializationSequence::FK_HLSLInitListFlatteningFailed); return; + } // C++ doesn't allow scalar initialization with more than one argument. // But C99 complex numbers are scalars and it makes sense there. @@ -6817,33 +6820,17 @@ void InitializationSequence::InitializeFrom(Sema &S, assert(Args.size() >= 1 && "Zero-argument case handled above"); // For HLSL ext vector types we allow list initialization behavior for C++ - // constructor syntax. This is accomplished by converting initialization - // arguments an InitListExpr late. + // functional cast expressions which look like constructor syntax. This is + // accomplished by converting initialization arguments an InitListExpr. if (S.getLangOpts().HLSL && Args.size() > 1 && DestType->isExtVectorType() && (SourceType.isNull() || !Context.hasSameUnqualifiedType(SourceType, DestType))) { - - llvm::SmallVector InitArgs; - for (auto *Arg : Args) { - if (Arg->getType()->isExtVectorType()) { - const auto *VTy = Arg->getType()->castAs(); - unsigned Elm = VTy->getNumElements(); - for (unsigned Idx = 0; Idx < Elm; ++Idx) { - InitArgs.emplace_back(new (Context) ArraySubscriptExpr( - Arg, - IntegerLiteral::Create( - Context, llvm::APInt(Context.getIntWidth(Context.IntTy), Idx), - Context.IntTy, SourceLocation()), - VTy->getElementType(), Arg->getValueKind(), Arg->getObjectKind(), - SourceLocation())); - } - } else - InitArgs.emplace_back(Arg); - } InitListExpr *ILE = new (Context) InitListExpr( - S.getASTContext(), SourceLocation(), InitArgs, SourceLocation()); + S.getASTContext(), SourceLocation(), Args, SourceLocation()); + ILE->setType(DestType); Args[0] = ILE; - AddListInitializationStep(DestType); + TryListInitialization(S, Entity, Kind, ILE, *this, + TreatUnavailableAsInvalid); return; } @@ -9302,6 +9289,14 @@ bool InitializationSequence::Diagnose(Sema &S, break; } + case InitializationSequence::FK_HLSLInitListFlatteningFailed: { + // Unlike C/C++ list initialization, there is no fallback if it fails. This + // allows us to diagnose the failure when it happens in the + // TryListInitialization call instead of delaying the diagnosis, which is + // beneficial because the flattening is also expnsive. + break; + } + case FK_ExplicitConstructor: { S.Diag(Kind.getLocation(), diag::err_selected_explicit_constructor) << Args[0]->getSourceRange(); @@ -9500,6 +9495,10 @@ void InitializationSequence::dump(raw_ostream &OS) const { case FK_DesignatedInitForNonAggregate: OS << "designated initializer for non-aggregate type"; break; + + case FK_HLSLInitListFlatteningFailed: + OS << "HLSL initialization list flattening failed"; + break; } OS << '\n'; return; diff --git a/clang/test/AST/HLSL/vector-constructors.hlsl b/clang/test/AST/HLSL/vector-constructors.hlsl index fd43a7dcbfcca..87b63df29d2de 100644 --- a/clang/test/AST/HLSL/vector-constructors.hlsl +++ b/clang/test/AST/HLSL/vector-constructors.hlsl @@ -28,11 +28,11 @@ void entry() { // CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' // CHECK-NEXT: ArraySubscriptExpr {{.*}} 'float' lvalue // CHECK-NEXT: DeclRefExpr {{.*}} 'float2':'vector' lvalue Var {{.*}} 'Vec2' 'float2':'vector' -// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 0 +// CHECK-NEXT: IntegerLiteral {{.*}} '__size_t':'unsigned long' 0 // CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' // CHECK-NEXT: ArraySubscriptExpr {{.*}} 'float' lvalue // CHECK-NEXT: DeclRefExpr {{.*}} 'float2':'vector' lvalue Var {{.*}} 'Vec2' 'float2':'vector' -// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1 +// CHECK-NEXT: IntegerLiteral {{.*}} '__size_t':'unsigned long' 1 // CHECK-NEXT: FloatingLiteral {{.*}} 'float' 3.000000e+00 // CHECK: VarDecl {{.*}} 'float3':'vector' cinit @@ -93,25 +93,6 @@ void entry() { // CHECK-NEXT: MemberExpr {{.*}} 'float' lvalue .f {{.*}} // CHECK-NEXT: DeclRefExpr {{.*}} 'struct S' lvalue Var {{.*}} 's' 'struct S' - struct T { - operator float() const { return 1.0f; } - } t; - float2 foo5 = float2(t, t); // user-defined cast operator - -// CHECK-LABEL: VarDecl {{.*}} foo5 'float2' -// CHECK-NEXT: CXXFunctionalCastExpr -// CHECK-NEXT: InitListExpr -// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' -// CHECK-NEXT: CXXMemberCallExpr {{.*}} 'float' -// CHECK-NEXT: MemberExpr {{.*}} '' .operator float {{.*}} -// CHECK-NEXT: ImplicitCastExpr {{.*}} 'const T' lvalue -// CHECK-NEXT: DeclRefExpr {{.*}} 'struct T' lvalue Var {{.*}} 't' 'struct T' -// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' -// CHECK-NEXT: CXXMemberCallExpr {{.*}} 'float' -// CHECK-NEXT: MemberExpr {{.*}} '' .operator float {{.*}} -// CHECK-NEXT: ImplicitCastExpr {{.*}} 'const T' lvalue -// CHECK-NEXT: DeclRefExpr {{.*}} 'struct T' lvalue Var {{.*}} 't' 'struct T' - typedef float2 second_level_of_typedefs; second_level_of_typedefs foo6 = float2(1.0f, 2.0f); diff --git a/clang/test/CodeGenHLSL/BasicFeatures/InitLists.hlsl b/clang/test/CodeGenHLSL/BasicFeatures/InitLists.hlsl index 7e83e5f168538..82ed7545cfdc5 100644 --- a/clang/test/CodeGenHLSL/BasicFeatures/InitLists.hlsl +++ b/clang/test/CodeGenHLSL/BasicFeatures/InitLists.hlsl @@ -61,10 +61,6 @@ struct EmptyDerived : Empty {}; struct UnnamedDerived : UnnamedOnly {}; -// CHECK-DAG: [[ConstE:@.*]] = private unnamed_addr constant %struct.Empty undef, align 1 -// CHECK-DAG: [[ConstUO:@.*]] = private unnamed_addr constant %struct.UnnamedOnly undef, align 1 -// CHECK-DAG: [[ConstED:@.*]] = private unnamed_addr constant %struct.EmptyDerived undef, align 1 -// CHECK-DAG: [[ConstUD:@.*]] = private unnamed_addr constant %struct.UnnamedDerived undef, align 1 // Case 1: Extraneous braces get ignored in literal instantiation. // CHECK-LABEL: define hidden void @_Z5case1v( @@ -911,15 +907,15 @@ TwoFloats case15(SlicyBits SB) { // CHECK-NEXT: [[X_ADDR:%.*]] = alloca ptr, align 4 // CHECK-NEXT: store ptr [[X]], ptr [[X_ADDR]], align 4 // CHECK-NEXT: [[X1:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOFLOATS]], ptr [[AGG_RESULT]], i32 0, i32 0 -// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[X_ADDR]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[X_ADDR]], align 4, !nonnull [[META3:![0-9]+]], !align [[META4:![0-9]+]] // CHECK-NEXT: [[TMP1:%.*]] = load float, ptr [[TMP0]], align 4 // CHECK-NEXT: store float [[TMP1]], ptr [[X1]], align 1 // CHECK-NEXT: [[Y:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOFLOATS]], ptr [[AGG_RESULT]], i32 0, i32 1 -// CHECK-NEXT: [[TMP2:%.*]] = load ptr, ptr [[X_ADDR]], align 4 +// CHECK-NEXT: [[TMP2:%.*]] = load ptr, ptr [[X_ADDR]], align 4, !nonnull [[META3]], !align [[META4]] // CHECK-NEXT: [[TMP3:%.*]] = load float, ptr [[TMP2]], align 4 // CHECK-NEXT: [[MUL:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[TMP3]], 1.500000e+00 // CHECK-NEXT: store float [[MUL]], ptr [[Y]], align 1 -// CHECK-NEXT: [[TMP4:%.*]] = load ptr, ptr [[X_ADDR]], align 4 +// CHECK-NEXT: [[TMP4:%.*]] = load ptr, ptr [[X_ADDR]], align 4, !nonnull [[META3]], !align [[META4]] // CHECK-NEXT: [[TMP5:%.*]] = load float, ptr [[TMP4]], align 4 // CHECK-NEXT: [[MUL2:%.*]] = fmul reassoc nnan ninf nsz arcp afn float [[TMP5]], 2.000000e+00 // CHECK-NEXT: store float [[MUL2]], ptr [[TMP4]], align 4 @@ -964,94 +960,173 @@ FourFloats case16() { } +// CHECK-LABEL: define hidden noundef i32 @_Z12case17Helperi( +// CHECK-SAME: i32 noundef [[X:%.*]]) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i32, align 4 +// CHECK-NEXT: store i32 [[X]], ptr [[X_ADDR]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[X_ADDR]], align 4 +// CHECK-NEXT: ret i32 [[TMP0]] +// int case17Helper(int x) { return x; } // InitList with OpaqueValueExpr -// CHECK-LABEL: define hidden void {{.*}}case17 -// CHECK: [[X:%.*]] = alloca <2 x i32>, align 8 -// CHECK-NEXT: [[C:%.*]] = call noundef i32 {{.*}}case17Helper{{.*}}(i32 noundef 0) -// CHECK-NEXT: [[C1:%.*]] = call noundef i32 {{.*}}case17Helper{{.*}}(i32 noundef 1) -// CHECK-NEXT: [[VI:%.*]] = insertelement <2 x i32> poison, i32 [[C]], i32 0 -// CHECK-NEXT: [[VI2:%.*]] = insertelement <2 x i32> [[VI]], i32 [[C1]], i32 1 -// CHECK-NEXT: store <2 x i32> [[VI2]], ptr [[X]], align 8 -// CHECK-NEXT: ret void +// CHECK-LABEL: define hidden void @_Z6case17v( +// CHECK-SAME: ) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[X:%.*]] = alloca <2 x i32>, align 8 +// CHECK-NEXT: [[CALL:%.*]] = call noundef i32 @_Z12case17Helperi(i32 noundef 0) #[[ATTR2]] +// CHECK-NEXT: [[CALL1:%.*]] = call noundef i32 @_Z12case17Helperi(i32 noundef 1) #[[ATTR2]] +// CHECK-NEXT: [[VECINIT:%.*]] = insertelement <2 x i32> poison, i32 [[CALL]], i32 0 +// CHECK-NEXT: [[VECINIT2:%.*]] = insertelement <2 x i32> [[VECINIT]], i32 [[CALL1]], i32 1 +// CHECK-NEXT: store <2 x i32> [[VECINIT2]], ptr [[X]], align 8 +// CHECK-NEXT: ret void +// void case17() { int2 X = {case17Helper(0), case17Helper(1)}; } // InitList with Struct with unnamed bitfield on LHS -// CHECK-LABEL: case18 -// CHECK: [[U:%.*]] = alloca %struct.Unnamed, align 1 -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[U]], ptr align 1 {{.*}}, i32 5, i1 false) +// CHECK-LABEL: define hidden void @_Z6case18v( +// CHECK-SAME: ) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[U:%.*]] = alloca [[STRUCT_UNNAMED:%.*]], align 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[U]], ptr align 1 @__const._Z6case18v.U, i32 5, i1 false) +// CHECK-NEXT: ret void +// void case18() { Unnamed U = {1}; } // InitList with Struct with unnamed bitfield on RHS -// CHECK-LABEL: case19 -// CHECK: [[TI:%.*]] = alloca %struct.TwoInts, align 1 -// CHECK-NEXT: [[Z:%.*]] = getelementptr inbounds nuw %struct.TwoInts, ptr [[TI]], i32 0, i32 0 -// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds nuw %struct.Unnamed, ptr %U, i32 0, i32 0 -// CHECK-NEXT: [[L:%.*]] = load i32, ptr [[A]], align 1 -// CHECK-NEXT: store i32 [[L]], ptr [[Z]], align 1 -// CHECK-NEXT: [[W:%.*]] = getelementptr inbounds nuw %struct.TwoInts, ptr [[TI]], i32 0, i32 1 -// CHECK-NEXT: store i32 1, ptr [[W]], align 1 +// CHECK-LABEL: define hidden void @_Z6case197Unnamed( +// CHECK-SAME: ptr noundef byval([[STRUCT_UNNAMED:%.*]]) align 1 [[U:%.*]]) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[TI:%.*]] = alloca [[STRUCT_TWOINTS:%.*]], align 1 +// CHECK-NEXT: [[Z:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 0 +// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_UNNAMED]], ptr [[U]], i32 0, i32 0 +// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[A]], align 1 +// CHECK-NEXT: store i32 [[TMP0]], ptr [[Z]], align 1 +// CHECK-NEXT: [[W:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 1 +// CHECK-NEXT: store i32 1, ptr [[W]], align 1 +// CHECK-NEXT: ret void +// void case19(Unnamed U) { TwoInts TI = {U, 1}; } // InitList with Empty Struct on LHS -// CHECK-LABEL: case20 -// CHECK: [[E:%.*]] = alloca %struct.Empty, align 1 -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[E]], ptr align 1 [[ConstE]], i32 1, i1 false) +// CHECK-LABEL: define hidden void @_Z6case20v( +// CHECK-SAME: ) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[E:%.*]] = alloca [[STRUCT_EMPTY:%.*]], align 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[E]], ptr align 1 @__const._Z6case20v.E, i32 1, i1 false) +// CHECK-NEXT: ret void +// void case20() { Empty E = {}; } // InitList with Empty Struct on RHS -// CHECK-LABEL: case21 -// CHECK: [[TI:%.*]] = alloca %struct.TwoInts, align 1 -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 %TI, ptr align 1 {{.*}}, i32 8, i1 false) +// CHECK-LABEL: define hidden void @_Z6case215Empty( +// CHECK-SAME: ptr noundef byval([[STRUCT_EMPTY:%.*]]) align 1 [[E:%.*]]) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[TI:%.*]] = alloca [[STRUCT_TWOINTS:%.*]], align 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[TI]], ptr align 1 @__const._Z6case215Empty.TI, i32 8, i1 false) +// CHECK-NEXT: ret void +// void case21(Empty E) { TwoInts TI = {E, 1, 2}; } // InitList with Struct with only unnamed bitfield on LHS -// CHECK-LABEL: case22 -// CHECK: [[UO:%.*]] = alloca %struct.UnnamedOnly, align 1 -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[UO]], ptr align 1 [[ConstUO]], i32 1, i1 false) +// CHECK-LABEL: define hidden void @_Z6case22v( +// CHECK-SAME: ) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[UO:%.*]] = alloca [[STRUCT_UNNAMEDONLY:%.*]], align 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[UO]], ptr align 1 @__const._Z6case22v.UO, i32 1, i1 false) +// CHECK-NEXT: ret void +// void case22() { - UnnamedOnly UO = {}; + UnnamedOnly UO = {}; } // InitList with Struct with only unnamed bitfield on RHS -// CHECK-LABEL: case23 -// CHECK: [[TI:%.*]] = alloca %struct.TwoInts, align 1 -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[TI]], ptr align 1 {{.*}}, i32 8, i1 false) +// CHECK-LABEL: define hidden void @_Z6case2311UnnamedOnly( +// CHECK-SAME: ptr noundef byval([[STRUCT_UNNAMEDONLY:%.*]]) align 1 [[UO:%.*]]) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[TI:%.*]] = alloca [[STRUCT_TWOINTS:%.*]], align 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[TI]], ptr align 1 @__const._Z6case2311UnnamedOnly.TI, i32 8, i1 false) +// CHECK-NEXT: ret void +// void case23(UnnamedOnly UO) { TwoInts TI = {UO, 1, 2}; } // InitList with Derived empty struct on LHS // InitList with Derived unnamed bitfield on LHS -// CHECK-LABEL: case24 -// CHECK: [[ED:%.*]] = alloca %struct.EmptyDerived, align 1 -// CHECK-NEXT: [[UD:%.*]] = alloca %struct.UnnamedDerived, align 1 -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 %ED, ptr align 1 [[ConstED]], i32 1, i1 false) -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 %UD, ptr align 1 [[ConstUD]], i32 1, i1 false) +// CHECK-LABEL: define hidden void @_Z6case24v( +// CHECK-SAME: ) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[ED:%.*]] = alloca [[STRUCT_EMPTYDERIVED:%.*]], align 1 +// CHECK-NEXT: [[UD:%.*]] = alloca [[STRUCT_UNNAMEDDERIVED:%.*]], align 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[ED]], ptr align 1 @__const._Z6case24v.ED, i32 1, i1 false) +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[UD]], ptr align 1 @__const._Z6case24v.UD, i32 1, i1 false) +// CHECK-NEXT: ret void +// void case24() { EmptyDerived ED = {}; UnnamedDerived UD = {}; } -// CHECK-LABEL: case25 -// CHECK: [[TI1:%.*]] = alloca %struct.TwoInts, align 1 -// CHECK-NEXT: [[TI2:%.*]] = alloca %struct.TwoInts, align 1 -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 %TI1, ptr align 1 {{.*}}, i32 8, i1 false) -// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 %TI2, ptr align 1 {{.*}}, i32 8, i1 false) +// CHECK-LABEL: define hidden void @_Z6case2512EmptyDerived14UnnamedDerived( +// CHECK-SAME: ptr noundef byval([[STRUCT_EMPTYDERIVED:%.*]]) align 1 [[ED:%.*]], ptr noundef byval([[STRUCT_UNNAMEDDERIVED:%.*]]) align 1 [[UD:%.*]]) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[TI1:%.*]] = alloca [[STRUCT_TWOINTS:%.*]], align 1 +// CHECK-NEXT: [[TI2:%.*]] = alloca [[STRUCT_TWOINTS]], align 1 +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[TI1]], ptr align 1 @__const._Z6case2512EmptyDerived14UnnamedDerived.TI1, i32 8, i1 false) +// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 1 [[TI2]], ptr align 1 @__const._Z6case2512EmptyDerived14UnnamedDerived.TI2, i32 8, i1 false) +// CHECK-NEXT: ret void +// void case25(EmptyDerived ED, UnnamedDerived UD) { TwoInts TI1 = {ED, 1, 2}; TwoInts TI2 = {UD, 1, 2}; } + +// CHECK-LABEL: define hidden void @_Z6case267TwoInts( +// CHECK-SAME: ptr noundef byval([[STRUCT_TWOINTS:%.*]]) align 1 [[TI:%.*]]) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[F:%.*]] = alloca <4 x float>, align 16 +// CHECK-NEXT: [[F2:%.*]] = alloca <3 x float>, align 16 +// CHECK-NEXT: [[Z:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 0 +// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[Z]], align 1 +// CHECK-NEXT: [[CONV:%.*]] = sitofp i32 [[TMP0]] to float +// CHECK-NEXT: [[VECINIT:%.*]] = insertelement <4 x float> poison, float [[CONV]], i32 0 +// CHECK-NEXT: [[W:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 1 +// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[W]], align 1 +// CHECK-NEXT: [[CONV1:%.*]] = sitofp i32 [[TMP1]] to float +// CHECK-NEXT: [[VECINIT2:%.*]] = insertelement <4 x float> [[VECINIT]], float [[CONV1]], i32 1 +// CHECK-NEXT: [[VECINIT3:%.*]] = insertelement <4 x float> [[VECINIT2]], float 1.000000e+00, i32 2 +// CHECK-NEXT: [[VECINIT4:%.*]] = insertelement <4 x float> [[VECINIT3]], float 2.000000e+00, i32 3 +// CHECK-NEXT: store <4 x float> [[VECINIT4]], ptr [[F]], align 16 +// CHECK-NEXT: [[Z5:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 0 +// CHECK-NEXT: [[TMP2:%.*]] = load i32, ptr [[Z5]], align 1 +// CHECK-NEXT: [[CONV6:%.*]] = sitofp i32 [[TMP2]] to float +// CHECK-NEXT: [[VECINIT7:%.*]] = insertelement <3 x float> , float [[CONV6]], i32 1 +// CHECK-NEXT: [[W8:%.*]] = getelementptr inbounds nuw [[STRUCT_TWOINTS]], ptr [[TI]], i32 0, i32 1 +// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[W8]], align 1 +// CHECK-NEXT: [[CONV9:%.*]] = sitofp i32 [[TMP3]] to float +// CHECK-NEXT: [[VECINIT10:%.*]] = insertelement <3 x float> [[VECINIT7]], float [[CONV9]], i32 2 +// CHECK-NEXT: store <3 x float> [[VECINIT10]], ptr [[F2]], align 16 +// CHECK-NEXT: ret void +// +void case26(TwoInts TI) { + float4 F = float4(TI, 1, 2); + float3 F2 = float3(3, TI); +} +//. +// CHECK: [[META3]] = !{} +// CHECK: [[META4]] = !{i64 4} +//. diff --git a/clang/test/SemaHLSL/BuiltIns/vector-constructors-erros.hlsl b/clang/test/SemaHLSL/BuiltIns/vector-constructors-erros.hlsl index b004acdc7c502..26133acb5832b 100644 --- a/clang/test/SemaHLSL/BuiltIns/vector-constructors-erros.hlsl +++ b/clang/test/SemaHLSL/BuiltIns/vector-constructors-erros.hlsl @@ -9,12 +9,23 @@ struct S2 { float f; int i; }; [numthreads(1,1,1)] void entry() { float2 LilVec = float2(1.0, 2.0); - float2 BrokenVec = float2(1.0, 2.0, 3.0); // expected-error{{excess elements in vector initializer}} - float3 NormieVec = float3(LilVec, 3.0, 4.0); // expected-error{{excess elements in vector initializer}} - float3 BrokenNormie = float3(3.0, 4.0); // expected-error{{too few elements in vector initialization (expected 3 elements, have 2)}} - float3 OverwhemledNormie = float3(3.0, 4.0, 5.0, 6.0); // expected-error{{excess elements in vector initializer}} + float2 BrokenVec = float2(1.0, 2.0, 3.0); // expected-error{{too many initializers in list for type 'float2' (vector of 2 'float' values) (expected 2 but found 3)}} + float3 NormieVec = float3(LilVec, 3.0, 4.0); // expected-error{{too many initializers in list for type 'float3' (vector of 3 'float' values) (expected 3 but found 4)}} + float3 BrokenNormie = float3(3.0, 4.0); // expected-error{{too few initializers in list for type 'float3' (vector of 3 'float' values) (expected 3 but found 2)}} + float3 OverwhemledNormie = float3(3.0, 4.0, 5.0, 6.0); // expected-error{{too many initializers in list for type 'float3' (vector of 3 'float' values) (expected 3 but found 4)}} - // These _should_ work in HLSL but aren't yet supported. + // These next two are a bit strange, but are consistent with HLSL today. S s; - float2 GettingStrange = float2(s, s); // expected-error{{no viable conversion from 'S' to 'float'}} expected-error{{no viable conversion from 'S' to 'float'}} + float2 GettingStrange = float2(s, s); + S2 s2 = {1.0f, 2}; + float2 AlsoStrange = float2(s2); + + float2 TooManyStruts = float2(s2, s); // expected-error{{too many initializers in list for type 'float2' (vector of 2 'float' values) (expected 2 but found 3)}} + + // HLSL does not yet allow user-defined conversions. + struct T { + operator float() const { return 1.0f; } + } t; + // TODO: Should this work? Today HLSL doesn't resolve user-defined conversions here, but we maybe should... + float2 foo5 = float2(t, t); // expected-error{{too few initializers in list for type 'float2' (vector of 2 'float' values) (expected 2 but found 0)}} } From 9011d22e8035c8e826a0849c5754d4219a8743cf Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Wed, 1 Oct 2025 12:55:56 -0500 Subject: [PATCH 2/4] Updates based on PR feedback. --- clang/lib/Sema/SemaInit.cpp | 5 +++-- clang/test/AST/HLSL/vector-constructors.hlsl | 2 +- ...nstructors-erros.hlsl => vector-constructors-errors.hlsl} | 0 3 files changed, 4 insertions(+), 3 deletions(-) rename clang/test/SemaHLSL/BuiltIns/{vector-constructors-erros.hlsl => vector-constructors-errors.hlsl} (100%) diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 6777f98737e3c..91d10a9e47f44 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -6825,8 +6825,9 @@ void InitializationSequence::InitializeFrom(Sema &S, if (S.getLangOpts().HLSL && Args.size() > 1 && DestType->isExtVectorType() && (SourceType.isNull() || !Context.hasSameUnqualifiedType(SourceType, DestType))) { - InitListExpr *ILE = new (Context) InitListExpr( - S.getASTContext(), SourceLocation(), Args, SourceLocation()); + InitListExpr *ILE = new (Context) + InitListExpr(S.getASTContext(), Args.front()->getBeginLoc(), Args, + Args.back()->getEndLoc()); ILE->setType(DestType); Args[0] = ILE; TryListInitialization(S, Entity, Kind, ILE, *this, diff --git a/clang/test/AST/HLSL/vector-constructors.hlsl b/clang/test/AST/HLSL/vector-constructors.hlsl index 87b63df29d2de..ab547554b148d 100644 --- a/clang/test/AST/HLSL/vector-constructors.hlsl +++ b/clang/test/AST/HLSL/vector-constructors.hlsl @@ -14,7 +14,7 @@ void entry() { // parameters to an initialization list // CHECK-LABEL: VarDecl {{.*}} used Vec2 'float2':'vector' cinit // CHECK-NEXT: CXXFunctionalCastExpr {{.*}} 'float2':'vector' functional cast to float2 -// CHECK-NEXT: InitListExpr {{.*}} 'float2':'vector' +// CHECK-NEXT: InitListExpr {{0x[0-9a-fA-F]+}} 'float2':'vector' // CHECK-NEXT: FloatingLiteral {{.*}} 'float' 1.000000e+00 // CHECK-NEXT: FloatingLiteral {{.*}} 'float' 2.000000e+00 diff --git a/clang/test/SemaHLSL/BuiltIns/vector-constructors-erros.hlsl b/clang/test/SemaHLSL/BuiltIns/vector-constructors-errors.hlsl similarity index 100% rename from clang/test/SemaHLSL/BuiltIns/vector-constructors-erros.hlsl rename to clang/test/SemaHLSL/BuiltIns/vector-constructors-errors.hlsl From 0ec071496daf0c02585662430bfe667901129f92 Mon Sep 17 00:00:00 2001 From: Chris B Date: Fri, 3 Oct 2025 17:44:28 -0500 Subject: [PATCH 3/4] Update clang/lib/Sema/SemaInit.cpp Co-authored-by: Helena Kotas --- clang/lib/Sema/SemaInit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 91d10a9e47f44..51d3df279e97c 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -6821,7 +6821,7 @@ void InitializationSequence::InitializeFrom(Sema &S, // For HLSL ext vector types we allow list initialization behavior for C++ // functional cast expressions which look like constructor syntax. This is - // accomplished by converting initialization arguments an InitListExpr. + // accomplished by converting initialization arguments to InitListExpr. if (S.getLangOpts().HLSL && Args.size() > 1 && DestType->isExtVectorType() && (SourceType.isNull() || !Context.hasSameUnqualifiedType(SourceType, DestType))) { From d9dd3ee61b0d2225e08238aab97bc5a5f2a73d34 Mon Sep 17 00:00:00 2001 From: Chris B Date: Fri, 3 Oct 2025 17:44:39 -0500 Subject: [PATCH 4/4] Update clang/lib/Sema/SemaInit.cpp Co-authored-by: Helena Kotas --- clang/lib/Sema/SemaInit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 51d3df279e97c..2bfc286da4325 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -9294,7 +9294,7 @@ bool InitializationSequence::Diagnose(Sema &S, // Unlike C/C++ list initialization, there is no fallback if it fails. This // allows us to diagnose the failure when it happens in the // TryListInitialization call instead of delaying the diagnosis, which is - // beneficial because the flattening is also expnsive. + // beneficial because the flattening is also expensive. break; }