Skip to content

Conversation

llvm-beanz
Copy link
Collaborator

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.

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.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" HLSL HLSL Language Support labels Sep 30, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 30, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-hlsl

Author: Chris B (llvm-beanz)

Changes

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.


Patch is 22.03 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/161421.diff

5 Files Affected:

  • (modified) clang/include/clang/Sema/Initialization.h (+3)
  • (modified) clang/lib/Sema/SemaInit.cpp (+22-23)
  • (modified) clang/test/AST/HLSL/vector-constructors.hlsl (+2-21)
  • (modified) clang/test/CodeGenHLSL/BasicFeatures/InitLists.hlsl (+124-49)
  • (modified) clang/test/SemaHLSL/BuiltIns/vector-constructors-erros.hlsl (+17-6)
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<Expr *> InitArgs;
-    for (auto *Arg : Args) {
-      if (Arg->getType()->isExtVectorType()) {
-        const auto *VTy = Arg->getType()->castAs<ExtVectorType>();
-        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' <LValueToRValue>
 // CHECK-NEXT: ArraySubscriptExpr {{.*}} 'float' lvalue
 // CHECK-NEXT: DeclRefExpr {{.*}} 'float2':'vector<float, 2>' lvalue Var {{.*}} 'Vec2' 'float2':'vector<float, 2>'
-// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 0
+// CHECK-NEXT: IntegerLiteral {{.*}} '__size_t':'unsigned long' 0
 // CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' <LValueToRValue>
 // CHECK-NEXT: ArraySubscriptExpr {{.*}} 'float' lvalue
 // CHECK-NEXT: DeclRefExpr {{.*}} 'float2':'vector<float, 2>' lvalue Var {{.*}} 'Vec2' 'float2':'vector<float, 2>'
-// CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1
+// CHECK-NEXT: IntegerLiteral {{.*}} '__size_t':'unsigned long' 1
 // CHECK-NEXT: FloatingLiteral {{.*}} 'float' 3.000000e+00
 
 // CHECK: VarDecl {{.*}} 'float3':'vector<float, 3>' 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' <UserDefinedConversion>
-// CHECK-NEXT: CXXMemberCallExpr {{.*}} 'float'
-// CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator float {{.*}}
-// CHECK-NEXT: ImplicitCastExpr {{.*}} 'const T' lvalue <NoOp>
-// CHECK-NEXT: DeclRefExpr {{.*}} 'struct T' lvalue Var {{.*}} 't' 'struct T'
-// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' <UserDefinedConversion>
-// CHECK-NEXT: CXXMemberCallExpr {{.*}} 'float'
-// CHECK-NEXT: MemberExpr {{.*}} '<bound member function type>' .operator float {{.*}}
-// CHECK-NEXT: ImplicitCastExpr {{.*}} 'const T' lvalue <NoOp>
-// 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 3.000000e+00, float poison, float poison>, 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...
[truncated]

Copy link
Member

@hekota hekota left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Just a couple of typos and some curious questions from me ;)

void entry() {
float2 LilVec = float2(1.0, 2.0);
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)}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called NormieVec when it is an error case? ;)

llvm-beanz and others added 2 commits October 3, 2025 17:44
Co-authored-by: Helena Kotas <hekotas@microsoft.com>
Co-authored-by: Helena Kotas <hekotas@microsoft.com>
@llvm-beanz llvm-beanz merged commit d3d7c3c into llvm:main Oct 6, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category HLSL HLSL Language Support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants