Author: Nathan Gauër
Date: 2026-02-19T10:08:20+01:00
New Revision: ae402a32c0f8d31b348b54c6e99b621328a1d71a

URL: 
https://github.com/llvm/llvm-project/commit/ae402a32c0f8d31b348b54c6e99b621328a1d71a
DIFF: 
https://github.com/llvm/llvm-project/commit/ae402a32c0f8d31b348b54c6e99b621328a1d71a.diff

LOG: [Clang][HLSL] Start emitting structured GEP instruction (#177332)

StructuredGEP is a new LLVM intrinsic which will allow to emit proper
logical SPIR-V or DXIL. To properly stage this change going across FE,
BE and optimizations, this commits adds a new flag:
  - `-fexperimental-emit-sgep`

When used, this flag will allow compatible frontends to emit the new
instructions. This will also allow us to migrate tests bit by bit,
adding the flag to each migrated test as we make progress on the
implementation.

Once the frontend migration complete, the flag will remain, and work on
the backend will start. Compatible backends like SPIR-V will first allow
both instructions, but then, depending on a target bit similar to
`requiresStructuredCFG`, will declare that they require the SGEP
instruction and will start enforcing it.
Once the whole chain completed, the flag will be defaulted to true and
removed, finishing the migration.

Added: 
    clang/test/CodeGenHLSL/sgep/array_load.hlsl
    clang/test/CodeGenHLSL/sgep/array_store.hlsl
    clang/test/CodeGenHLSL/sgep/load_global.hlsl
    clang/test/CodeGenHLSL/sgep/object_method.hlsl

Modified: 
    clang/include/clang/Basic/LangOptions.def
    clang/include/clang/Options/Options.td
    clang/lib/CodeGen/CGExpr.cpp
    clang/lib/CodeGen/CGHLSLRuntime.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/LangOptions.def 
b/clang/include/clang/Basic/LangOptions.def
index 08f102839b89e..75543e0d78f9c 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -249,6 +249,7 @@ LANGOPT(HLSLStrictAvailability, 1, 0, NotCompatible,
         "Strict availability diagnostic mode for HLSL built-in functions.")
 LANGOPT(HLSLSpvUseUnknownImageFormat, 1, 0, NotCompatible, "For storage images 
and texel buffers, sets the default format to 'Unknown' when not specified via 
the `vk::image_format` attribute. If this option is not used, the format is 
inferred from the resource's data type.")
 LANGOPT(HLSLSpvEnableMaximalReconvergence, 1, 0, NotCompatible, "Enables the 
MaximallyReconvergesKHR execution mode for this module. This ensures that 
control flow reconverges at well-defined merge points as defined by the Vulkan 
spec.")
+LANGOPT(EmitStructuredGEP, 1, 0, NotCompatible, "Emit structured GEP 
intrinsics instead of GEP instructions")
 
 LANGOPT(CUDAIsDevice      , 1, 0, NotCompatible, "compiling for CUDA device")
 LANGOPT(CUDAHostDeviceConstexpr, 1, 1, NotCompatible, "treating unattributed 
constexpr functions as __host__ __device__")

diff  --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index c8fb2a55fe7ac..e3724cda55ff8 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -9872,6 +9872,13 @@ def fhlsl_spv_enable_maximal_reconvergence
                "well-defined merge points as defined by the Vulkan spec.">,
       MarshallingInfoFlag<LangOpts<"HLSLSpvEnableMaximalReconvergence">>;
 
+def fexperimental_emit_sgep
+    : Flag<["-"], "fexperimental-emit-sgep">,
+      Visibility<[CC1Option, DXCOption]>,
+      HelpText<"Emit structured GEP intrinsic instead of GEP instructions "
+               "(experimental).">,
+      MarshallingInfoFlag<LangOpts<"EmitStructuredGEP">>;
+
 def no_wasm_opt : Flag<["--"], "no-wasm-opt">,
   Group<m_Group>,
   HelpText<"Disable the wasm-opt optimizer">,

diff  --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index eb778f3583a98..473a657204776 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -4557,7 +4557,16 @@ Address CodeGenFunction::EmitArrayToPointerDecay(const 
Expr *E,
   if (!E->getType()->isVariableArrayType()) {
     assert(isa<llvm::ArrayType>(Addr.getElementType()) &&
            "Expected pointer to array");
-    Addr = Builder.CreateConstArrayGEP(Addr, 0, "arraydecay");
+
+    if (getLangOpts().EmitStructuredGEP) {
+      // Array-to-pointer decay for an SGEP is a no-op as we don't do any
+      // logical indexing. See #179951 for some additional context.
+      auto *SGEP =
+          Builder.CreateStructuredGEP(NewTy, Addr.emitRawPointer(*this), {});
+      Addr = Address(SGEP, NewTy, Addr.getAlignment(), Addr.isKnownNonNull());
+    } else {
+      Addr = Builder.CreateConstArrayGEP(Addr, 0, "arraydecay");
+    }
   }
 
   // The result of this decay conversion points to an array element within the
@@ -4596,6 +4605,9 @@ static llvm::Value *emitArraySubscriptGEP(CodeGenFunction 
&CGF,
                                           bool signedIndices,
                                           SourceLocation loc,
                                     const llvm::Twine &name = "arrayidx") {
+  if (inbounds && CGF.getLangOpts().EmitStructuredGEP)
+    return CGF.Builder.CreateStructuredGEP(elemType, ptr, indices);
+
   if (inbounds) {
     return CGF.EmitCheckedInBoundsGEP(elemType, ptr, indices, signedIndices,
                                       CodeGenFunction::NotSubtraction, loc,
@@ -4607,10 +4619,17 @@ static llvm::Value 
*emitArraySubscriptGEP(CodeGenFunction &CGF,
 
 static Address emitArraySubscriptGEP(CodeGenFunction &CGF, Address addr,
                                      ArrayRef<llvm::Value *> indices,
+                                     llvm::Type *arrayType,
                                      llvm::Type *elementType, bool inbounds,
                                      bool signedIndices, SourceLocation loc,
                                      CharUnits align,
                                      const llvm::Twine &name = "arrayidx") {
+  if (inbounds && CGF.getLangOpts().EmitStructuredGEP)
+    return RawAddress(CGF.Builder.CreateStructuredGEP(arrayType,
+                                                      addr.emitRawPointer(CGF),
+                                                      indices.drop_front()),
+                      elementType, align);
+
   if (inbounds) {
     return CGF.EmitCheckedInBoundsGEP(addr, indices, elementType, 
signedIndices,
                                       CodeGenFunction::NotSubtraction, loc,
@@ -4727,6 +4746,8 @@ static Address emitArraySubscriptGEP(CodeGenFunction 
&CGF, Address addr,
   if (!LastIndex ||
       (!CGF.IsInPreservedAIRegion && !IsPreserveAIArrayBase(CGF, Base))) {
     addr = emitArraySubscriptGEP(CGF, addr, indices,
+                                 arrayType ? CGF.ConvertTypeForMem(*arrayType)
+                                           : nullptr,
                                  CGF.ConvertTypeForMem(eltType), inbounds,
                                  signedIndices, loc, eltAlign, name);
     return addr;
@@ -5581,6 +5602,14 @@ static Address emitAddrOfFieldStorage(CodeGenFunction 
&CGF, Address base,
 
   unsigned idx =
     CGF.CGM.getTypes().getCGRecordLayout(rec).getLLVMFieldNo(field);
+  llvm::Type *StructType =
+      CGF.CGM.getTypes().getCGRecordLayout(rec).getLLVMType();
+
+  if (CGF.getLangOpts().EmitStructuredGEP)
+    return RawAddress(
+        CGF.Builder.CreateStructuredGEP(StructType, base.emitRawPointer(CGF),
+                                        {CGF.Builder.getSize(idx)}),
+        base.getElementType(), base.getAlignment());
 
   if (!IsInBounds)
     return CGF.Builder.CreateConstGEP2_32(base, 0, idx, field->getName());

diff  --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp 
b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 805f7a8b4445b..79d709867dc02 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -36,6 +36,7 @@
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/DerivedTypes.h"
 #include "llvm/IR/GlobalVariable.h"
+#include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Metadata.h"
 #include "llvm/IR/Module.h"
@@ -1410,21 +1411,38 @@ std::optional<LValue> 
CGHLSLRuntime::emitBufferArraySubscriptExpr(
 
   LValueBaseInfo EltBaseInfo;
   TBAAAccessInfo EltTBAAInfo;
-  Address Addr =
-      CGF.EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo);
-  llvm::Value *Idx = EmitIdxAfterBase(/*Promote*/ true);
 
   // Index into the object as-if we have an array of the padded element type,
   // and then dereference the element itself to avoid reading padding that may
   // be past the end of the in-memory object.
   SmallVector<llvm::Value *, 2> Indices;
+  llvm::Value *Idx = EmitIdxAfterBase(/*Promote*/ true);
   Indices.push_back(Idx);
   Indices.push_back(llvm::ConstantInt::get(CGF.Int32Ty, 0));
 
+  if (CGF.getLangOpts().EmitStructuredGEP) {
+    // The fact that we emit an array-to-pointer decay might be an oversight,
+    // but for now, we simply ignore it (see #179951).
+    const CastExpr *CE = cast<CastExpr>(E->getBase());
+    assert(CE->getCastKind() == CastKind::CK_ArrayToPointerDecay);
+
+    LValue LV = CGF.EmitLValue(CE->getSubExpr());
+    Address Addr = LV.getAddress();
+    LayoutTy = llvm::ArrayType::get(
+        LayoutTy,
+        cast<llvm::ArrayType>(Addr.getElementType())->getNumElements());
+    auto *GEP = cast<StructuredGEPInst>(CGF.Builder.CreateStructuredGEP(
+        LayoutTy, Addr.emitRawPointer(CGF), Indices, "cbufferidx"));
+    Addr =
+        Address(GEP, GEP->getResultElementType(), RowAlignedSize, 
KnownNonNull);
+    return CGF.MakeAddrLValue(Addr, E->getType(), EltBaseInfo, EltTBAAInfo);
+  }
+
+  Address Addr =
+      CGF.EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo);
   llvm::Value *GEP = CGF.Builder.CreateGEP(LayoutTy, Addr.emitRawPointer(CGF),
                                            Indices, "cbufferidx");
   Addr = Address(GEP, Addr.getElementType(), RowAlignedSize, KnownNonNull);
-
   return CGF.MakeAddrLValue(Addr, E->getType(), EltBaseInfo, EltTBAAInfo);
 }
 
@@ -1616,9 +1634,14 @@ LValue 
CGHLSLRuntime::emitBufferMemberExpr(CodeGenFunction &CGF,
   llvm::Type *FieldLLVMTy = CGM.getTypes().ConvertTypeForMem(FieldType);
   CharUnits Align = CharUnits::fromQuantity(
       CGF.CGM.getDataLayout().getABITypeAlign(FieldLLVMTy));
-  Address Addr(CGF.Builder.CreateStructGEP(LayoutTy, Base.getPointer(CGF),
-                                           FieldIdx, Field->getName()),
-               FieldLLVMTy, Align, KnownNonNull);
+
+  Value *Ptr = CGF.getLangOpts().EmitStructuredGEP
+                   ? CGF.Builder.CreateStructuredGEP(
+                         LayoutTy, Base.getPointer(CGF),
+                         llvm::ConstantInt::get(CGM.IntTy, FieldIdx))
+                   : CGF.Builder.CreateStructGEP(LayoutTy, 
Base.getPointer(CGF),
+                                                 FieldIdx, Field->getName());
+  Address Addr(Ptr, FieldLLVMTy, Align, KnownNonNull);
 
   LValue LV = LValue::MakeAddr(Addr, FieldType, CGM.getContext(),
                                LValueBaseInfo(AlignmentSource::Type),

diff  --git a/clang/test/CodeGenHLSL/sgep/array_load.hlsl 
b/clang/test/CodeGenHLSL/sgep/array_load.hlsl
new file mode 100644
index 0000000000000..fa2feca1ae961
--- /dev/null
+++ b/clang/test/CodeGenHLSL/sgep/array_load.hlsl
@@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm 
-finclude-default-header -fexperimental-emit-sgep -disable-llvm-passes -o - %s 
| FileCheck %s
+// RUN: %clang_cc1 -triple spirv-linux-vulkan-library -x hlsl -emit-llvm 
-finclude-default-header -disable-llvm-passes -fexperimental-emit-sgep -o - %s 
| FileCheck %s
+
+void foo() {
+// CHECK: %array = alloca [3 x i32], align 4
+  uint array[3] = { 0, 1, 2 };
+
+// CHECK: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype([3 x i32]) %array, {{i32|i64}} 2)
+// CHECK: load i32, ptr %[[#PTR]], align 4
+  uint tmp = array[2];
+}
+
+struct S {
+  uint a;
+  uint b;
+};
+
+void bar() {
+// CHECK: %array = alloca [3 x %struct.S], align 1
+  S array[3] = { { 0, 1 }, { 2, 3 }, { 3, 4 } };
+
+// CHECK: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype([3 x %struct.S]) %array, {{i32|i64}} 2)
+// CHECK: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype(%struct.S) %[[#A]], {{i32|i64}} 1)
+// CHECK: load i32, ptr %[[#B]], align 1
+  uint tmp = array[2].b;
+}
+
+struct S2 {
+  uint a;
+  S b;
+  uint c;
+};
+
+void baz() {
+// CHECK: %array = alloca [2 x %struct.S2], align 1
+  S2 array[2] = { { 0, { 1, 2 }, 3 }, { 4, { 5, 6 }, 7 } };
+
+// CHECK: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype([2 x %struct.S2]) %array, {{i32|i64}} 1)
+// CHECK: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype(%struct.S2) %[[#A]], {{i32|i64}} 1)
+// CHECK: %[[#C:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype(%struct.S) %[[#B]], {{i32|i64}} 0)
+// CHECK: load i32, ptr %[[#C]], align 1
+  uint tmp = array[1].b.a;
+}

diff  --git a/clang/test/CodeGenHLSL/sgep/array_store.hlsl 
b/clang/test/CodeGenHLSL/sgep/array_store.hlsl
new file mode 100644
index 0000000000000..f08d2b1f5d258
--- /dev/null
+++ b/clang/test/CodeGenHLSL/sgep/array_store.hlsl
@@ -0,0 +1,47 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm 
-finclude-default-header -fexperimental-emit-sgep -disable-llvm-passes -o - %s 
| FileCheck %s
+// RUN: %clang_cc1 -triple spirv-linux-vulkan-library -x hlsl -emit-llvm 
-finclude-default-header -disable-llvm-passes -fexperimental-emit-sgep -o - %s 
| FileCheck %s
+
+[shader("compute")]
+[numthreads(1,1,1)]
+void foo() {
+// CHECK: %array = alloca [10 x i32], align 4
+  uint array[10];
+
+// CHECK: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype([10 x i32]) %array, {{i32|i64}} 2)
+// CHECK: store i32 10, ptr %[[#PTR]], align 4
+  array[2] = 10;
+}
+
+struct S {
+  uint a;
+  uint b;
+};
+
+void bar() {
+// CHECK: %array = alloca [3 x %struct.S], align 1
+  S array[3] = { { 0, 1 }, { 2, 3 }, { 3, 4 } };
+
+// CHECK: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype([3 x %struct.S]) %array, {{i32|i64}} 2)
+// CHECK: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype(%struct.S) %[[#A]], {{i32|i64}} 1)
+// CHECK: store i32 10, ptr %[[#B]], align 1
+
+  array[2].b = 10;
+}
+
+struct S2 {
+  uint a;
+  S b;
+  uint c;
+};
+
+void baz() {
+// CHECK: %array = alloca [2 x %struct.S2], align 1
+  S2 array[2] = { { 0, { 1, 2 }, 3 }, { 4, { 5, 6 }, 7 } };
+
+// CHECK: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype([2 x %struct.S2]) %array, {{i32|i64}} 1)
+// CHECK: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype(%struct.S2) %[[#A]], {{i32|i64}} 1)
+// CHECK: %[[#C:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype(%struct.S) %[[#B]], {{i32|i64}} 0)
+// CHECK: store i32 10, ptr %[[#C]], align 1
+
+  array[1].b.a = 10;
+}

diff  --git a/clang/test/CodeGenHLSL/sgep/load_global.hlsl 
b/clang/test/CodeGenHLSL/sgep/load_global.hlsl
new file mode 100644
index 0000000000000..18e57e04e64fc
--- /dev/null
+++ b/clang/test/CodeGenHLSL/sgep/load_global.hlsl
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm 
-finclude-default-header -fexperimental-emit-sgep -disable-llvm-passes -o - %s 
| FileCheck %s --check-prefixes=CHECK-DXIL
+// RUN: %clang_cc1 -triple spirv-linux-vulkan-library -x hlsl -emit-llvm 
-finclude-default-header -disable-llvm-passes -fexperimental-emit-sgep -o - %s 
| FileCheck %s --check-prefixes=CHECK-SPIR
+
+struct S {
+  uint a;
+  uint b;
+  uint c;
+  uint d;
+};
+
+// CHECK-DXIL: @_ZL1s = external hidden addrspace(2) global %struct.S, align 1
+// CHECK-DXIL: @_ZL1a = external hidden addrspace(2) constant [4 x i32], align 
4
+// CHECK-DXIL: @_ZL1b = external hidden addrspace(2) constant i32, align 4
+
+// CHECK-SPIR: @_ZL1s = external hidden addrspace(12) global %struct.S, align 1
+// CHECK-SPIR: @_ZL1a = external hidden addrspace(12) constant [4 x i32], 
align 4
+// CHECK-SPIR: @_ZL1b = external hidden addrspace(12) constant i32, align 4
+const S s;
+const uint a[4];
+const uint b;
+
+void foo() {
+
+// CHECK-DXIL: %[[#PTR:]] = call ptr addrspace(2) (ptr addrspace(2), ...) 
@llvm.structured.gep.p2(ptr addrspace(2) elementtype(%S) @_ZL1s, i32 1)
+// CHECK-DXIL: %[[#]] = load i32, ptr addrspace(2) %[[#PTR]], align 4
+
+// CHECK-SPIR: %[[#PTR:]] = call ptr addrspace(12) (ptr addrspace(12), ...) 
@llvm.structured.gep.p12(ptr addrspace(12) elementtype(%S) @_ZL1s, i32 1)
+// CHECK-SPIR: %[[#]] = load i32, ptr addrspace(12) %[[#PTR]], align 4
+  uint tmp = s.b;
+}
+
+void bar() {
+// CHECK-DXIL: %cbufferidx = call ptr addrspace(2) (ptr addrspace(2), ...) 
@llvm.structured.gep.p2(ptr addrspace(2) elementtype([4 x <{ i32, 
target("dx.Padding", 12) }>]) @_ZL1a, i32 2, i32 0)
+// CHECK-DXIL: %[[#]] = load i32, ptr addrspace(2) %cbufferidx, align 16
+
+// CHECK-SPIR: %cbufferidx = call ptr addrspace(12) (ptr addrspace(12), ...) 
@llvm.structured.gep.p12(ptr addrspace(12) elementtype([4 x <{ i32, 
target("spirv.Padding", 12) }>]) @_ZL1a, i64 2, i32 0)
+// CHECK-SPIR: %[[#]] = load i32, ptr addrspace(12) %cbufferidx, align 16
+  uint tmp = a[2];
+}
+
+void baz() {
+// CHECK-DXIL: %[[#]] = load i32, ptr addrspace(2) @_ZL1b, align 4
+// CHECK-SPIR: %[[#]] = load i32, ptr addrspace(12) @_ZL1b, align 4
+  uint tmp = b;
+}

diff  --git a/clang/test/CodeGenHLSL/sgep/object_method.hlsl 
b/clang/test/CodeGenHLSL/sgep/object_method.hlsl
new file mode 100644
index 0000000000000..fae271e26d0f9
--- /dev/null
+++ b/clang/test/CodeGenHLSL/sgep/object_method.hlsl
@@ -0,0 +1,30 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm 
-finclude-default-header -fexperimental-emit-sgep -disable-llvm-passes -o - %s 
| FileCheck %s --check-prefixes=CHECK,CHECK-DXIL
+// RUN: %clang_cc1 -triple spirv-linux-vulkan-library -x hlsl -emit-llvm 
-finclude-default-header -disable-llvm-passes -fexperimental-emit-sgep -o - %s 
| FileCheck %s --check-prefixes=CHECK,CHECK-SPIR
+
+struct O {
+  int value = 0;
+
+  int get() {
+    return value;
+  }
+};
+
+[shader("compute")]
+[numthreads(1,1,1)]
+void foo() {
+  O o;
+
+// CHECK:      %o = alloca %struct.O, align 1
+// CHECK-DXIL: call noundef i32 @_ZN1O3getEv(ptr noundef nonnull align 1 
dereferenceable(4) %o)
+// CHECK-SPIR: call spir_func noundef i32 @_ZN1O3getEv(ptr noundef nonnull 
align 1 dereferenceable(4) %o)
+  uint tmp = o.get();
+}
+
+
+
+// CHECK-DXIL: define linkonce_odr hidden noundef i32 @_ZN1O3getEv(ptr noundef 
nonnull align 1 dereferenceable(4) %this)
+// CHECK-SPIR: define linkonce_odr hidden spir_func noundef i32 
@_ZN1O3getEv(ptr noundef nonnull align 1 dereferenceable(4) %this)
+
+// CHECK: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr 
elementtype(%struct.O) %this1, {{i32|i64}} 0)
+// CHECK: %[[#B:]] = load i32, ptr %[[#A]], align 1
+// CHECK:            ret i32 %[[#B]]


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to