[clang] [NFC][clang][bytecode][HLSL] Refactor HLSL helper functions to use a common visitor class (PR #194393)

Deric C. via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 27 07:56:40 PDT 2026


https://github.com/Icohedron created https://github.com/llvm/llvm-project/pull/194393

Addressing the code repetition concerns pointed out by @shafik in https://github.com/llvm/llvm-project/pull/189126#discussion_r3141480667

This PR refactors `emitHLSLAggregateSplat`, `countHLSLFlatElements`, `emitHLSLFlattenAggregate`, and `emitHLSLConstructAggregate` to reduce code repetition by creating a common `HLSLElementStoreVisitor` class for visiting HLSL aggregate types.

Assisted-by: Claude Opus 4.6

>From c58afed2ee405c497f9b540fd174f2fd254d81a4 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Sun, 26 Apr 2026 23:26:04 +0000
Subject: [PATCH 1/8] Add HLSLAggregateVisitor CRTP base class

Introduce a reusable CRTP base class HLSLAggregateVisitor that walks the
scalar elements of HLSL aggregate types (vectors, matrices, arrays,
records). Derived classes override visit* hooks to customize behavior at
each node.

This is preparatory infrastructure for refactoring the HLSL cast
helpers to use this visitor pattern, eliminating duplicated traversal
logic.

Assisted-by: Claude Opus 4.6
---
 clang/lib/AST/ByteCode/Compiler.h | 105 ++++++++++++++++++++++++++++++
 1 file changed, 105 insertions(+)

diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index a80f19e07e70a..a890c71f6069c 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -41,6 +41,9 @@ template <class Emitter> class LabelScope;
 template <class Emitter> class SwitchScope;
 template <class Emitter> class StmtExprScope;
 template <class Emitter> class LocOverrideScope;
+template <class Emitter> class HLSLElementStoreVisitor;
+template <class Emitter> class HLSLFlatElementCounter;
+template <class Emitter> class HLSLElementFlattenVisitor;
 
 template <class Emitter> class Compiler;
 struct InitLink {
@@ -353,6 +356,9 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
   friend class SwitchScope<Emitter>;
   friend class StmtExprScope<Emitter>;
   friend class LocOverrideScope<Emitter>;
+  friend class HLSLElementStoreVisitor<Emitter>;
+  friend class HLSLFlatElementCounter<Emitter>;
+  friend class HLSLElementFlattenVisitor<Emitter>;
 
   /// Emits a zero initializer.
   bool visitZeroInitializer(PrimType T, QualType QT, const Expr *E);
@@ -422,6 +428,105 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
   bool emitLambdaStaticInvokerBody(const CXXMethodDecl *MD);
   bool emitBuiltinBitCast(const CastExpr *E);
 
+  /// Base class for visiting the scalar elements of an HLSL aggregate type
+  /// (vectors, matrices, arrays, records).  Derived classes override the
+  /// \c visit* methods to customise behaviour at each node.  The walker
+  /// calls:
+  ///
+  ///  - \c visitScalarElem   for each scalar element of a vector, matrix,
+  ///                         or primitive array.
+  ///  - \c visitArrayComposite for each composite element of an array.
+  ///  - \c visitBase         for each base class of a record.
+  ///  - \c visitField        for each scalar field of a record.
+  ///  - \c visitFieldComposite for each composite field of a record.
+  ///
+  /// Composite visitors receive the sub-type's \c QualType and must
+  /// call \c visit(SubType) themselves to recurse.
+  template <typename Derived> class HLSLAggregateVisitor {
+  public:
+    explicit HLSLAggregateVisitor(Compiler<Emitter> &C) : C(C) {}
+
+    bool visit(QualType Ty) {
+      // Vectors and matrices are flat sequences of scalar elements.
+      unsigned NumElems = 0;
+      QualType ElemType;
+      if (const auto *VT = Ty->template getAs<VectorType>()) {
+        NumElems = VT->getNumElements();
+        ElemType = VT->getElementType();
+      } else if (const auto *MT = Ty->template getAs<ConstantMatrixType>()) {
+        NumElems = MT->getNumElementsFlattened();
+        ElemType = MT->getElementType();
+      }
+      if (NumElems > 0) {
+        PrimType ElemT = C.classifyPrim(ElemType);
+        for (unsigned I = 0; I != NumElems; ++I) {
+          if (!derived().visitScalarElem(ElemType, ElemT, I))
+            return false;
+        }
+        return true;
+      }
+
+      // Arrays.
+      if (const auto *AT = Ty->getAsArrayTypeUnsafe()) {
+        const auto *CAT = cast<ConstantArrayType>(AT);
+        QualType ArrElemType = CAT->getElementType();
+        unsigned ArrSize = CAT->getZExtSize();
+
+        if (OptPrimType ElemT = C.classify(ArrElemType)) {
+          for (unsigned I = 0; I != ArrSize; ++I) {
+            if (!derived().visitScalarElem(ArrElemType, *ElemT, I))
+              return false;
+          }
+        } else {
+          for (unsigned I = 0; I != ArrSize; ++I) {
+            if (!derived().visitArrayComposite(ArrElemType, I))
+              return false;
+          }
+        }
+        return true;
+      }
+
+      // Records.
+      if (Ty->isRecordType()) {
+        const Record *R = C.getRecord(Ty);
+        if (!R)
+          return false;
+
+        if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(R->getDecl())) {
+          for (const CXXBaseSpecifier &BS : CXXRD->bases()) {
+            const Record::Base *B = R->getBase(BS.getType());
+            assert(B);
+            if (!derived().visitBase(BS.getType(), B))
+              return false;
+          }
+        }
+
+        for (const Record::Field &F : R->fields()) {
+          if (F.isUnnamedBitField())
+            continue;
+          QualType FieldType = F.Decl->getType();
+          if (OptPrimType FieldT = C.classify(FieldType)) {
+            if (!derived().visitField(FieldType, *FieldT, &F))
+              return false;
+          } else {
+            if (!derived().visitFieldComposite(FieldType, &F))
+              return false;
+          }
+        }
+        return true;
+      }
+
+      // All other types: abort visit.
+      return false;
+    }
+
+  protected:
+    Compiler<Emitter> &C;
+
+  private:
+    Derived &derived() { return static_cast<Derived &>(*this); }
+  };
+
   bool emitHLSLAggregateSplat(PrimType SrcT, unsigned SrcOffset,
                               QualType DestType, const Expr *E);
 

>From b74adf44e142a1bf26f8429d844400d95628eb78 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Sun, 26 Apr 2026 23:26:04 +0000
Subject: [PATCH 2/8] Add HLSLElementStoreVisitor

Add a concrete HLSLAggregateVisitor subclass that stores values into an
HLSL destination type.  It accepts a ProduceValue callback that places
a value on the interpreter stack, then stores it with the appropriate
InitElem / InitField / InitBitField opcode.

This visitor will be used in subsequent commits to replace the
hand-rolled traversals in emitHLSLAggregateSplat and
emitHLSLConstructAggregate.

Assisted-by: Claude Opus 4.6
---
 clang/lib/AST/ByteCode/Compiler.cpp | 71 +++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index d1878cbedae58..5f5b04e074e2e 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -8018,6 +8018,77 @@ bool Compiler<Emitter>::emitBuiltinBitCast(const CastExpr *E) {
   return true;
 }
 
+namespace clang {
+namespace interp {
+
+/// Visitor that stores values into an HLSL destination type.
+/// ProduceValue must leave exactly one value of the requested type on the
+/// interpreter stack; the visitor then stores it with the appropriate
+/// InitElem / InitField / etc. opcode.
+template <class Emitter>
+class HLSLElementStoreVisitor
+    : public Compiler<Emitter>::template HLSLAggregateVisitor<
+          HLSLElementStoreVisitor<Emitter>> {
+  using VisitorBase = typename Compiler<Emitter>::template HLSLAggregateVisitor<
+      HLSLElementStoreVisitor<Emitter>>;
+
+public:
+  HLSLElementStoreVisitor(
+      Compiler<Emitter> &C,
+      llvm::function_ref<bool(PrimType, QualType, const Expr *)> ProduceValue,
+      const Expr *E)
+      : VisitorBase(C), ProduceValue(ProduceValue), E(E) {}
+
+  bool visitScalarElem(QualType ElemType, PrimType ElemT, unsigned I) {
+    if (!ProduceValue(ElemT, ElemType, E))
+      return false;
+    return this->C.emitInitElem(ElemT, I, E);
+  }
+
+  bool visitArrayComposite(QualType ElemType, unsigned I) {
+    if (!this->C.emitConstUint32(I, E))
+      return false;
+    if (!this->C.emitArrayElemPtrUint32(E))
+      return false;
+    if (!this->visit(ElemType))
+      return false;
+    return this->C.emitFinishInitPop(E);
+  }
+
+  bool visitBase(QualType BaseType, const Record::Base *B) {
+    if (!this->C.emitGetPtrBase(B->Offset, E))
+      return false;
+    if (!this->visit(BaseType))
+      return false;
+    return this->C.emitFinishInitPop(E);
+  }
+
+  bool visitField(QualType FieldType, PrimType FieldT, const Record::Field *F) {
+    if (!ProduceValue(FieldT, FieldType, E))
+      return false;
+    if (F->isBitField())
+      return this->C.emitInitBitField(FieldT, F->Offset, F->bitWidth(), E);
+    return this->C.emitInitField(FieldT, F->Offset, E);
+  }
+
+  bool visitFieldComposite(QualType FieldType, const Record::Field *F) {
+    if (!this->C.emitGetPtrField(F->Offset, E))
+      return false;
+    if (!this->visit(FieldType))
+      return false;
+    return this->C.emitPopPtr(E);
+  }
+
+private:
+  /// Non-owning — the visitor must not outlive the callable passed at
+  /// construction.
+  llvm::function_ref<bool(PrimType, QualType, const Expr *)> ProduceValue;
+  const Expr *E;
+};
+
+} // namespace interp
+} // namespace clang
+
 /// Replicate a scalar value into every scalar element of an aggregate.
 /// The scalar is stored in a local at \p SrcOffset and a pointer to the
 /// destination must be on top of the interpreter stack. Each element receives

>From 306a77bec04ff34bac4d21075e03e1fd1b26d575 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Sun, 26 Apr 2026 23:26:04 +0000
Subject: [PATCH 3/8] Use HLSLElementStoreVisitor in emitHLSLAggregateSplat

Replace the hand-rolled recursive type traversal in
emitHLSLAggregateSplat with a short delegation to
HLSLElementStoreVisitor.

Assisted-by: Claude Opus 4.6
---
 clang/lib/AST/ByteCode/Compiler.cpp | 110 ++--------------------------
 1 file changed, 7 insertions(+), 103 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 5f5b04e074e2e..dd37a691b425e 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -8098,110 +8098,14 @@ bool Compiler<Emitter>::emitHLSLAggregateSplat(PrimType SrcT,
                                                unsigned SrcOffset,
                                                QualType DestType,
                                                const Expr *E) {
-  // Vectors and matrices are treated as flat sequences of elements.
-  unsigned NumElems = 0;
-  QualType ElemType;
-  if (const auto *VT = DestType->getAs<VectorType>()) {
-    NumElems = VT->getNumElements();
-    ElemType = VT->getElementType();
-  } else if (const auto *MT = DestType->getAs<ConstantMatrixType>()) {
-    NumElems = MT->getNumElementsFlattened();
-    ElemType = MT->getElementType();
-  }
-  if (NumElems > 0) {
-    PrimType ElemT = classifyPrim(ElemType);
-    for (unsigned I = 0; I != NumElems; ++I) {
-      if (!this->emitGetLocal(SrcT, SrcOffset, E))
-        return false;
-      if (!this->emitPrimCast(SrcT, ElemT, ElemType, E))
-        return false;
-      if (!this->emitInitElem(ElemT, I, E))
-        return false;
-    }
-    return true;
-  }
-
-  // Arrays: primitive elements are filled directly; composite elements
-  // require recursion into each sub-aggregate.
-  if (const auto *AT = DestType->getAsArrayTypeUnsafe()) {
-    const auto *CAT = cast<ConstantArrayType>(AT);
-    QualType ArrElemType = CAT->getElementType();
-    unsigned ArrSize = CAT->getZExtSize();
-
-    if (OptPrimType ElemT = classify(ArrElemType)) {
-      for (unsigned I = 0; I != ArrSize; ++I) {
-        if (!this->emitGetLocal(SrcT, SrcOffset, E))
-          return false;
-        if (!this->emitPrimCast(SrcT, *ElemT, ArrElemType, E))
-          return false;
-        if (!this->emitInitElem(*ElemT, I, E))
-          return false;
-      }
-    } else {
-      for (unsigned I = 0; I != ArrSize; ++I) {
-        if (!this->emitConstUint32(I, E))
-          return false;
-        if (!this->emitArrayElemPtrUint32(E))
-          return false;
-        if (!emitHLSLAggregateSplat(SrcT, SrcOffset, ArrElemType, E))
-          return false;
-        if (!this->emitFinishInitPop(E))
-          return false;
-      }
-    }
-    return true;
-  }
-
-  // Records: fill base classes first, then named fields in declaration
-  // order.
-  if (DestType->isRecordType()) {
-    const Record *R = getRecord(DestType);
-    if (!R)
+  auto ProduceValue = [&](PrimType DestT, QualType DestQT,
+                          const Expr *E) -> bool {
+    if (!this->emitGetLocal(SrcT, SrcOffset, E))
       return false;
-
-    if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(R->getDecl())) {
-      for (const CXXBaseSpecifier &BS : CXXRD->bases()) {
-        const Record::Base *B = R->getBase(BS.getType());
-        assert(B);
-        if (!this->emitGetPtrBase(B->Offset, E))
-          return false;
-        if (!emitHLSLAggregateSplat(SrcT, SrcOffset, BS.getType(), E))
-          return false;
-        if (!this->emitFinishInitPop(E))
-          return false;
-      }
-    }
-
-    for (const Record::Field &F : R->fields()) {
-      if (F.isUnnamedBitField())
-        continue;
-
-      QualType FieldType = F.Decl->getType();
-      if (OptPrimType FieldT = classify(FieldType)) {
-        if (!this->emitGetLocal(SrcT, SrcOffset, E))
-          return false;
-        if (!this->emitPrimCast(SrcT, *FieldT, FieldType, E))
-          return false;
-        if (F.isBitField()) {
-          if (!this->emitInitBitField(*FieldT, F.Offset, F.bitWidth(), E))
-            return false;
-        } else {
-          if (!this->emitInitField(*FieldT, F.Offset, E))
-            return false;
-        }
-      } else {
-        if (!this->emitGetPtrField(F.Offset, E))
-          return false;
-        if (!emitHLSLAggregateSplat(SrcT, SrcOffset, FieldType, E))
-          return false;
-        if (!this->emitPopPtr(E))
-          return false;
-      }
-    }
-    return true;
-  }
-
-  return false;
+    return this->emitPrimCast(SrcT, DestT, DestQT, E);
+  };
+  HLSLElementStoreVisitor<Emitter> W(*this, ProduceValue, E);
+  return W.visit(DestType);
 }
 
 /// Return the total number of scalar elements in a type. This is used

>From 3a2abeea689723a6c400aba3becab7c32caa6abe Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Sun, 26 Apr 2026 23:26:04 +0000
Subject: [PATCH 4/8] Add HLSLFlatElementCounter

Add a concrete HLSLAggregateVisitor subclass that counts the total
number of scalar elements in an HLSL type by incrementing a counter
at each scalar leaf.

This visitor will be used in the next commit to replace the
hand-rolled recursive counting in countHLSLFlatElements.

Assisted-by: Claude Opus 4.6
---
 clang/lib/AST/ByteCode/Compiler.cpp | 35 +++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index dd37a691b425e..62f77436403dc 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -8086,6 +8086,41 @@ class HLSLElementStoreVisitor
   const Expr *E;
 };
 
+/// Visitor that counts the total number of scalar elements in an HLSL type.
+template <class Emitter>
+class HLSLFlatElementCounter
+    : public Compiler<Emitter>::template HLSLAggregateVisitor<
+          HLSLFlatElementCounter<Emitter>> {
+  using VisitorBase = typename Compiler<Emitter>::template HLSLAggregateVisitor<
+      HLSLFlatElementCounter<Emitter>>;
+
+public:
+  explicit HLSLFlatElementCounter(Compiler<Emitter> &C) : VisitorBase(C) {}
+
+  unsigned getCount() const { return Count; }
+
+  bool visitScalarElem(QualType, PrimType, unsigned) {
+    ++Count;
+    return true;
+  }
+  bool visitArrayComposite(QualType ElemType, unsigned) {
+    return this->visit(ElemType);
+  }
+  bool visitBase(QualType BaseType, const Record::Base *) {
+    return this->visit(BaseType);
+  }
+  bool visitField(QualType, PrimType, const Record::Field *) {
+    ++Count;
+    return true;
+  }
+  bool visitFieldComposite(QualType FieldType, const Record::Field *) {
+    return this->visit(FieldType);
+  }
+
+private:
+  unsigned Count = 0;
+};
+
 } // namespace interp
 } // namespace clang
 

>From ff5d19df14ff302415ba19bfc3ba974edff83672 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Sun, 26 Apr 2026 23:26:04 +0000
Subject: [PATCH 5/8] Use HLSLFlatElementCounter in countHLSLFlatElements

Replace the hand-rolled recursive element counting in
countHLSLFlatElements with a short delegation to
HLSLFlatElementCounter.

Assisted-by: Claude Opus 4.6
---
 clang/lib/AST/ByteCode/Compiler.cpp | 33 ++++-------------------------
 1 file changed, 4 insertions(+), 29 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 62f77436403dc..c9e851aede905 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -8148,37 +8148,12 @@ bool Compiler<Emitter>::emitHLSLAggregateSplat(PrimType SrcT,
 /// so we never flatten more than the destination can hold.
 template <class Emitter>
 unsigned Compiler<Emitter>::countHLSLFlatElements(QualType Ty) {
-  // Vector and matrix types are treated as flat sequences of elements.
-  if (const auto *VT = Ty->getAs<VectorType>())
-    return VT->getNumElements();
-  if (const auto *MT = Ty->getAs<ConstantMatrixType>())
-    return MT->getNumElementsFlattened();
-  // Arrays: total count is array size * scalar elements per element.
-  if (const auto *AT = Ty->getAsArrayTypeUnsafe()) {
-    const auto *CAT = cast<ConstantArrayType>(AT);
-    return CAT->getZExtSize() * countHLSLFlatElements(CAT->getElementType());
-  }
-  // Records: sum scalar element counts of base classes and named fields.
-  if (Ty->isRecordType()) {
-    const Record *R = getRecord(Ty);
-    if (!R)
-      return 0;
-    unsigned Count = 0;
-    if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(R->getDecl())) {
-      for (const CXXBaseSpecifier &BS : CXXRD->bases())
-        Count += countHLSLFlatElements(BS.getType());
-    }
-    for (const Record::Field &F : R->fields()) {
-      if (F.isUnnamedBitField())
-        continue;
-      Count += countHLSLFlatElements(F.Decl->getType());
-    }
-    return Count;
-  }
-  // Scalar primitive types contribute one element.
   if (canClassify(Ty))
     return 1;
-  return 0;
+  HLSLFlatElementCounter<Emitter> Counter(*this);
+  if (!Counter.visit(Ty))
+    return 0;
+  return Counter.getCount();
 }
 
 /// Walk a source aggregate and extract every scalar element into its own local

>From 7406c6cec0237cd30dd1a0240d3b39550fa67d49 Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Sun, 26 Apr 2026 23:26:04 +0000
Subject: [PATCH 6/8] Add HLSLElementFlattenVisitor

Add a concrete HLSLAggregateVisitor subclass that extracts every
scalar element of a source value into its own local variable.  It
tracks a MaxElements cap and uses SaveAndRestore to manage the
current source pointer offset across recursive sub-aggregate calls.

This visitor will be used in the next commit to replace the
hand-rolled recursive extraction in emitHLSLFlattenAggregate.

Assisted-by: Claude Opus 4.6
---
 clang/lib/AST/ByteCode/Compiler.cpp | 106 ++++++++++++++++++++++++++++
 1 file changed, 106 insertions(+)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index c9e851aede905..e9d6744bcfdab 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -8121,6 +8121,112 @@ class HLSLFlatElementCounter
   unsigned Count = 0;
 };
 
+/// Visitor that extracts every scalar element of a source value into
+/// its own local variable.
+template <class Emitter>
+class HLSLElementFlattenVisitor
+    : public Compiler<Emitter>::template HLSLAggregateVisitor<
+          HLSLElementFlattenVisitor<Emitter>> {
+  using VisitorBase = typename Compiler<Emitter>::template HLSLAggregateVisitor<
+      HLSLElementFlattenVisitor<Emitter>>;
+  using HLSLFlatElement = typename Compiler<Emitter>::HLSLFlatElement;
+
+public:
+  HLSLElementFlattenVisitor(Compiler<Emitter> &C, unsigned SrcOffset,
+                            SmallVectorImpl<HLSLFlatElement> &Elements,
+                            unsigned MaxElements, const Expr *E)
+      : VisitorBase(C), CurrentSrcOffset(SrcOffset), Elements(Elements),
+        MaxElements(MaxElements), E(E) {}
+
+  bool isDone() const { return Done; }
+
+  bool visitScalarElem(QualType, PrimType ElemT, unsigned I) {
+    if (checkDone())
+      return false;
+    if (!this->C.emitGetLocal(PT_Ptr, CurrentSrcOffset, E))
+      return false;
+    if (!this->C.emitArrayElemPop(ElemT, I, E))
+      return false;
+    return saveToLocal(ElemT);
+  }
+
+  bool visitArrayComposite(QualType ElemType, unsigned I) {
+    if (checkDone())
+      return false;
+    if (!this->C.emitGetLocal(PT_Ptr, CurrentSrcOffset, E))
+      return false;
+    if (!this->C.emitConstUint32(I, E))
+      return false;
+    if (!this->C.emitArrayElemPtrPopUint32(E))
+      return false;
+    return enterSubAggregate(ElemType);
+  }
+
+  bool visitBase(QualType BaseType, const Record::Base *B) {
+    if (checkDone())
+      return false;
+    if (!this->C.emitGetLocal(PT_Ptr, CurrentSrcOffset, E))
+      return false;
+    if (!this->C.emitGetPtrBasePop(B->Offset, /*NullOK=*/false, E))
+      return false;
+    return enterSubAggregate(BaseType);
+  }
+
+  bool visitField(QualType, PrimType FieldT, const Record::Field *F) {
+    if (checkDone())
+      return false;
+    if (!this->C.emitGetLocal(PT_Ptr, CurrentSrcOffset, E))
+      return false;
+    if (!this->C.emitGetPtrFieldPop(F->Offset, E))
+      return false;
+    if (!this->C.emitLoadPop(FieldT, E))
+      return false;
+    return saveToLocal(FieldT);
+  }
+
+  bool visitFieldComposite(QualType FieldType, const Record::Field *F) {
+    if (checkDone())
+      return false;
+    if (!this->C.emitGetLocal(PT_Ptr, CurrentSrcOffset, E))
+      return false;
+    if (!this->C.emitGetPtrFieldPop(F->Offset, E))
+      return false;
+    return enterSubAggregate(FieldType);
+  }
+
+private:
+  bool checkDone() {
+    if (Done || Elements.size() >= MaxElements) {
+      Done = true;
+      return true;
+    }
+    return false;
+  }
+
+  bool saveToLocal(PrimType T) {
+    unsigned Off = this->C.allocateLocalPrimitive(E, T, /*IsConst=*/true);
+    if (!this->C.emitSetLocal(T, Off, E))
+      return false;
+    Elements.push_back({Off, T});
+    return true;
+  }
+
+  bool enterSubAggregate(QualType SubType) {
+    unsigned Offset =
+        this->C.allocateLocalPrimitive(E, PT_Ptr, /*IsConst=*/true);
+    if (!this->C.emitSetLocal(PT_Ptr, Offset, E))
+      return false;
+    llvm::SaveAndRestore SrcOffsetScope(CurrentSrcOffset, Offset);
+    return this->visit(SubType);
+  }
+
+  unsigned CurrentSrcOffset;
+  SmallVectorImpl<HLSLFlatElement> &Elements;
+  unsigned MaxElements;
+  const Expr *E;
+  bool Done = false;
+};
+
 } // namespace interp
 } // namespace clang
 

>From 11bacb40ca6297c6042f47b008df8e7ed4e1ebed Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Sun, 26 Apr 2026 23:26:04 +0000
Subject: [PATCH 7/8] Use HLSLElementFlattenVisitor in emitHLSLFlattenAggregate

Replace the hand-rolled recursive extraction in
emitHLSLFlattenAggregate with a short delegation to
HLSLElementFlattenVisitor.

Assisted-by: Claude Opus 4.6
---
 clang/lib/AST/ByteCode/Compiler.cpp | 136 +---------------------------
 1 file changed, 4 insertions(+), 132 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index e9d6744bcfdab..452ba21340e61 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -8271,138 +8271,10 @@ bool Compiler<Emitter>::emitHLSLFlattenAggregate(
     QualType SrcType, unsigned SrcOffset,
     SmallVectorImpl<HLSLFlatElement> &Elements, unsigned MaxElements,
     const Expr *E) {
-
-  // Save a scalar value from the stack into a new local and record it.
-  auto saveToLocal = [&](PrimType T) -> bool {
-    unsigned Offset = allocateLocalPrimitive(E, T, /*IsConst=*/true);
-    if (!this->emitSetLocal(T, Offset, E))
-      return false;
-    Elements.push_back({Offset, T});
-    return true;
-  };
-
-  // Save a pointer from the stack into a new local for later use.
-  auto savePtrToLocal = [&]() -> UnsignedOrNone {
-    unsigned Offset = allocateLocalPrimitive(E, PT_Ptr, /*IsConst=*/true);
-    if (!this->emitSetLocal(PT_Ptr, Offset, E))
-      return std::nullopt;
-    return Offset;
-  };
-
-  // Vectors and matrices are flat sequences of elements.
-  unsigned NumElems = 0;
-  QualType ElemType;
-  if (const auto *VT = SrcType->getAs<VectorType>()) {
-    NumElems = VT->getNumElements();
-    ElemType = VT->getElementType();
-  } else if (const auto *MT = SrcType->getAs<ConstantMatrixType>()) {
-    NumElems = MT->getNumElementsFlattened();
-    ElemType = MT->getElementType();
-  }
-  if (NumElems > 0) {
-    PrimType ElemT = classifyPrim(ElemType);
-    for (unsigned I = 0; I != NumElems && Elements.size() < MaxElements; ++I) {
-      if (!this->emitGetLocal(PT_Ptr, SrcOffset, E))
-        return false;
-      if (!this->emitArrayElemPop(ElemT, I, E))
-        return false;
-      if (!saveToLocal(ElemT))
-        return false;
-    }
-    return true;
-  }
-
-  // Arrays: primitive elements are extracted directly; composite elements
-  // require recursion into each sub-aggregate.
-  if (const auto *AT = SrcType->getAsArrayTypeUnsafe()) {
-    const auto *CAT = cast<ConstantArrayType>(AT);
-    QualType ArrElemType = CAT->getElementType();
-    unsigned ArrSize = CAT->getZExtSize();
-
-    if (OptPrimType ElemT = classify(ArrElemType)) {
-      for (unsigned I = 0; I != ArrSize && Elements.size() < MaxElements; ++I) {
-        if (!this->emitGetLocal(PT_Ptr, SrcOffset, E))
-          return false;
-        if (!this->emitArrayElemPop(*ElemT, I, E))
-          return false;
-        if (!saveToLocal(*ElemT))
-          return false;
-      }
-    } else {
-      for (unsigned I = 0; I != ArrSize && Elements.size() < MaxElements; ++I) {
-        if (!this->emitGetLocal(PT_Ptr, SrcOffset, E))
-          return false;
-        if (!this->emitConstUint32(I, E))
-          return false;
-        if (!this->emitArrayElemPtrPopUint32(E))
-          return false;
-        UnsignedOrNone ElemPtrOffset = savePtrToLocal();
-        if (!ElemPtrOffset)
-          return false;
-        if (!emitHLSLFlattenAggregate(ArrElemType, *ElemPtrOffset, Elements,
-                                      MaxElements, E))
-          return false;
-      }
-    }
-    return true;
-  }
-
-  // Records: base classes come first, then named fields in declaration
-  // order.
-  if (SrcType->isRecordType()) {
-    const Record *R = getRecord(SrcType);
-    if (!R)
-      return false;
-
-    if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(R->getDecl())) {
-      for (const CXXBaseSpecifier &BS : CXXRD->bases()) {
-        if (Elements.size() >= MaxElements)
-          break;
-        const Record::Base *B = R->getBase(BS.getType());
-        assert(B);
-        if (!this->emitGetLocal(PT_Ptr, SrcOffset, E))
-          return false;
-        if (!this->emitGetPtrBasePop(B->Offset, /*NullOK=*/false, E))
-          return false;
-        UnsignedOrNone BasePtrOffset = savePtrToLocal();
-        if (!BasePtrOffset)
-          return false;
-        if (!emitHLSLFlattenAggregate(BS.getType(), *BasePtrOffset, Elements,
-                                      MaxElements, E))
-          return false;
-      }
-    }
-
-    for (const Record::Field &F : R->fields()) {
-      if (Elements.size() >= MaxElements)
-        break;
-      if (F.isUnnamedBitField())
-        continue;
-
-      QualType FieldType = F.Decl->getType();
-      if (!this->emitGetLocal(PT_Ptr, SrcOffset, E))
-        return false;
-      if (!this->emitGetPtrFieldPop(F.Offset, E))
-        return false;
-
-      if (OptPrimType FieldT = classify(FieldType)) {
-        if (!this->emitLoadPop(*FieldT, E))
-          return false;
-        if (!saveToLocal(*FieldT))
-          return false;
-      } else {
-        UnsignedOrNone FieldPtrOffset = savePtrToLocal();
-        if (!FieldPtrOffset)
-          return false;
-        if (!emitHLSLFlattenAggregate(FieldType, *FieldPtrOffset, Elements,
-                                      MaxElements, E))
-          return false;
-      }
-    }
-    return true;
-  }
-
-  return false;
+  HLSLElementFlattenVisitor<Emitter> W(*this, SrcOffset, Elements, MaxElements,
+                                       E);
+  bool Result = W.visit(SrcType);
+  return W.isDone() || Result;
 }
 
 /// Populate an HLSL aggregate from a flat list of previously extracted source

>From 2ad73afaa2dcdc60a59374a49c6b82ba104bd3ae Mon Sep 17 00:00:00 2001
From: Deric Cheung <cheung.deric at gmail.com>
Date: Sun, 26 Apr 2026 23:26:04 +0000
Subject: [PATCH 8/8] Use HLSLElementStoreVisitor in emitHLSLConstructAggregate

Replace the hand-rolled recursive type traversal in
emitHLSLConstructAggregate with the existing HLSLElementStoreVisitor.
The loadAndCast lambda becomes a ProduceValue callback that indexes
into the flat element list. This also simplifies the API by removing
the two-overload pattern (with/without ElemIdx); the index is now
a local variable inside the single remaining overload.

Assisted-by: Claude Opus 4.6
---
 clang/lib/AST/ByteCode/Compiler.cpp | 115 +++-------------------------
 clang/lib/AST/ByteCode/Compiler.h   |   8 +-
 2 files changed, 10 insertions(+), 113 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 452ba21340e61..a1f46f0631fbf 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -8277,122 +8277,25 @@ bool Compiler<Emitter>::emitHLSLFlattenAggregate(
   return W.isDone() || Result;
 }
 
-/// Populate an HLSL aggregate from a flat list of previously extracted source
-/// elements, casting each to the corresponding destination element type.
+/// Populate an HLSL aggregate from a flat list of previously extracted
+/// source elements, casting each to the corresponding destination element type.
 /// \p ElemIdx tracks the current position in \p Elements and is advanced as
 /// elements are consumed. A pointer to the destination must be on top of the
 /// interpreter stack.
 template <class Emitter>
 bool Compiler<Emitter>::emitHLSLConstructAggregate(
-    QualType DestType, ArrayRef<HLSLFlatElement> Elements, unsigned &ElemIdx,
-    const Expr *E) {
-
-  // Consume the next source element, cast it, and leave it on the stack.
-  auto loadAndCast = [&](PrimType DestT, QualType DestQT) -> bool {
+    QualType DestType, ArrayRef<HLSLFlatElement> Elements, const Expr *E) {
+  unsigned ElemIdx = 0;
+  auto ProduceValue = [&](PrimType DestT, QualType DestQT,
+                          const Expr *E) -> bool {
+    assert(ElemIdx < Elements.size() && "Element index out of bounds");
     const auto &Src = Elements[ElemIdx++];
     if (!this->emitGetLocal(Src.Type, Src.LocalOffset, E))
       return false;
     return this->emitPrimCast(Src.Type, DestT, DestQT, E);
   };
-
-  // Vectors and matrices are flat sequences of elements.
-  unsigned NumElems = 0;
-  QualType ElemType;
-  if (const auto *VT = DestType->getAs<VectorType>()) {
-    NumElems = VT->getNumElements();
-    ElemType = VT->getElementType();
-  } else if (const auto *MT = DestType->getAs<ConstantMatrixType>()) {
-    NumElems = MT->getNumElementsFlattened();
-    ElemType = MT->getElementType();
-  }
-  if (NumElems > 0) {
-    PrimType DestElemT = classifyPrim(ElemType);
-    for (unsigned I = 0; I != NumElems; ++I) {
-      if (!loadAndCast(DestElemT, ElemType))
-        return false;
-      if (!this->emitInitElem(DestElemT, I, E))
-        return false;
-    }
-    return true;
-  }
-
-  // Arrays: primitive elements are filled directly; composite elements
-  // require recursion into each sub-aggregate.
-  if (const auto *AT = DestType->getAsArrayTypeUnsafe()) {
-    const auto *CAT = cast<ConstantArrayType>(AT);
-    QualType ArrElemType = CAT->getElementType();
-    unsigned ArrSize = CAT->getZExtSize();
-
-    if (OptPrimType ElemT = classify(ArrElemType)) {
-      for (unsigned I = 0; I != ArrSize; ++I) {
-        if (!loadAndCast(*ElemT, ArrElemType))
-          return false;
-        if (!this->emitInitElem(*ElemT, I, E))
-          return false;
-      }
-    } else {
-      for (unsigned I = 0; I != ArrSize; ++I) {
-        if (!this->emitConstUint32(I, E))
-          return false;
-        if (!this->emitArrayElemPtrUint32(E))
-          return false;
-        if (!emitHLSLConstructAggregate(ArrElemType, Elements, ElemIdx, E))
-          return false;
-        if (!this->emitFinishInitPop(E))
-          return false;
-      }
-    }
-    return true;
-  }
-
-  // Records: base classes come first, then named fields in declaration
-  // order.
-  if (DestType->isRecordType()) {
-    const Record *R = getRecord(DestType);
-    if (!R)
-      return false;
-
-    if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(R->getDecl())) {
-      for (const CXXBaseSpecifier &BS : CXXRD->bases()) {
-        const Record::Base *B = R->getBase(BS.getType());
-        assert(B);
-        if (!this->emitGetPtrBase(B->Offset, E))
-          return false;
-        if (!emitHLSLConstructAggregate(BS.getType(), Elements, ElemIdx, E))
-          return false;
-        if (!this->emitFinishInitPop(E))
-          return false;
-      }
-    }
-
-    for (const Record::Field &F : R->fields()) {
-      if (F.isUnnamedBitField())
-        continue;
-
-      QualType FieldType = F.Decl->getType();
-      if (OptPrimType FieldT = classify(FieldType)) {
-        if (!loadAndCast(*FieldT, FieldType))
-          return false;
-        if (F.isBitField()) {
-          if (!this->emitInitBitField(*FieldT, F.Offset, F.bitWidth(), E))
-            return false;
-        } else {
-          if (!this->emitInitField(*FieldT, F.Offset, E))
-            return false;
-        }
-      } else {
-        if (!this->emitGetPtrField(F.Offset, E))
-          return false;
-        if (!emitHLSLConstructAggregate(FieldType, Elements, ElemIdx, E))
-          return false;
-        if (!this->emitPopPtr(E))
-          return false;
-      }
-    }
-    return true;
-  }
-
-  return false;
+  HLSLElementStoreVisitor<Emitter> W(*this, ProduceValue, E);
+  return W.visit(DestType);
 }
 
 namespace clang {
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index a890c71f6069c..576c59ad8ac88 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -541,13 +541,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
                                 unsigned MaxElements, const Expr *E);
   bool emitHLSLConstructAggregate(QualType DestType,
                                   ArrayRef<HLSLFlatElement> Elements,
-                                  unsigned &ElemIdx, const Expr *E);
-  bool emitHLSLConstructAggregate(QualType DestType,
-                                  ArrayRef<HLSLFlatElement> Elements,
-                                  const Expr *E) {
-    unsigned ElemIdx = 0;
-    return emitHLSLConstructAggregate(DestType, Elements, ElemIdx, E);
-  }
+                                  const Expr *E);
 
   bool compileConstructor(const CXXConstructorDecl *Ctor);
   bool compileDestructor(const CXXDestructorDecl *Dtor);



More information about the cfe-commits mailing list