[clang] [llvm] [HLSL][Clang] Start emitting @llvm.structured.alloca (PR #190157)

Nathan Gauër via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 2 05:06:09 PDT 2026


https://github.com/Keenuts created https://github.com/llvm/llvm-project/pull/190157

Allowing some pattterns in the FE to emit this new instruction to emit logical pointers. Renamed the experimental-emit-sgep flag to reflect the broader logic it gates.
This also updates the few frontend tests to reflect the newly emitted alloca.

Next step is to handle the Mem2Reg/Reg2Mem.

>From 9146b20a4016a868b7258c62b1f3d30a0f3f5627 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Thu, 2 Apr 2026 11:47:13 +0200
Subject: [PATCH] [HLSL][Clang] Start emitting @llvm.structured.alloca

Allowing some pattterns in the FE to emit this new instruction to
emit logical pointers. Renamed the experimental-emit-sgep flag to
reflect the broader logic it gates.
---
 clang/include/clang/Basic/LangOptions.def      |  2 +-
 clang/include/clang/Options/Options.td         | 10 +++++-----
 clang/lib/CodeGen/CGExpr.cpp                   | 15 ++++++++++-----
 clang/lib/CodeGen/CGHLSLRuntime.cpp            | 15 +++++++++++----
 clang/test/CodeGenHLSL/sgep/array_load.hlsl    | 10 +++++-----
 clang/test/CodeGenHLSL/sgep/array_store.hlsl   | 10 +++++-----
 clang/test/CodeGenHLSL/sgep/load_global.hlsl   |  4 ++--
 clang/test/CodeGenHLSL/sgep/object_method.hlsl |  6 +++---
 llvm/include/llvm/IR/IRBuilder.h               | 10 ++++++++++
 9 files changed, 52 insertions(+), 30 deletions(-)

diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index dd4c5a653d38b..ee790b5c1cc3a 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -249,7 +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(EmitLogicalPointer, 1, 0, NotCompatible, "Allow emitting structured GEP/alloca intrinsics instead of normal GEP/alloca 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 412683fd968b0..5dd67d7db5ef2 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -10073,12 +10073,12 @@ 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">,
+def fexperimental_logical_pointer
+    : Flag<["-"], "fexperimental-logical-pointer">,
       Visibility<[CC1Option, DXCOption]>,
-      HelpText<"Emit structured GEP intrinsic instead of GEP instructions "
-               "(experimental).">,
-      MarshallingInfoFlag<LangOpts<"EmitStructuredGEP">>;
+      HelpText<"Allow emitting structured GEP/alloca intrinsics instead of "
+               "normal GEP/alloca instructions (experimental).">,
+      MarshallingInfoFlag<LangOpts<"EmitLogicalPointer">>;
 
 def no_wasm_opt : Flag<["--"], "no-wasm-opt">,
   Group<m_Group>,
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 23802cdeb4811..cdd46fc100f07 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -107,7 +107,12 @@ RawAddress
 CodeGenFunction::CreateTempAllocaWithoutCast(llvm::Type *Ty, CharUnits Align,
                                              const Twine &Name,
                                              llvm::Value *ArraySize) {
-  auto Alloca = CreateTempAlloca(Ty, Name, ArraySize);
+  if (getLangOpts().EmitLogicalPointer) {
+    auto Alloca = Builder.CreateStructuredAlloca(Ty, Name);
+    return RawAddress(Alloca, Ty, Align, KnownNonNull);
+  }
+
+  auto *Alloca = CreateTempAlloca(Ty, Name, ArraySize);
   Alloca->setAlignment(Align.getAsAlign());
   return RawAddress(Alloca, Ty, Align, KnownNonNull);
 }
@@ -4602,7 +4607,7 @@ Address CodeGenFunction::EmitArrayToPointerDecay(const Expr *E,
     assert(isa<llvm::ArrayType>(Addr.getElementType()) &&
            "Expected pointer to array");
 
-    if (getLangOpts().EmitStructuredGEP) {
+    if (getLangOpts().EmitLogicalPointer) {
       // 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 =
@@ -4649,7 +4654,7 @@ static llvm::Value *emitArraySubscriptGEP(CodeGenFunction &CGF,
                                           bool signedIndices,
                                           SourceLocation loc,
                                     const llvm::Twine &name = "arrayidx") {
-  if (inbounds && CGF.getLangOpts().EmitStructuredGEP)
+  if (inbounds && CGF.getLangOpts().EmitLogicalPointer)
     return CGF.Builder.CreateStructuredGEP(elemType, ptr, indices);
 
   if (inbounds) {
@@ -4668,7 +4673,7 @@ static Address emitArraySubscriptGEP(CodeGenFunction &CGF, Address addr,
                                      bool signedIndices, SourceLocation loc,
                                      CharUnits align,
                                      const llvm::Twine &name = "arrayidx") {
-  if (inbounds && CGF.getLangOpts().EmitStructuredGEP)
+  if (inbounds && CGF.getLangOpts().EmitLogicalPointer)
     return RawAddress(CGF.Builder.CreateStructuredGEP(arrayType,
                                                       addr.emitRawPointer(CGF),
                                                       indices.drop_front()),
@@ -5661,7 +5666,7 @@ static Address emitRawAddrOfFieldStorage(CodeGenFunction &CGF, Address base,
   llvm::Type *StructType =
       CGF.CGM.getTypes().getCGRecordLayout(rec).getLLVMType();
 
-  if (CGF.getLangOpts().EmitStructuredGEP)
+  if (CGF.getLangOpts().EmitLogicalPointer)
     return RawAddress(
         CGF.Builder.CreateStructuredGEP(StructType, base.emitRawPointer(CGF),
                                         {CGF.Builder.getSize(idx)}),
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 6182663111f5a..1e25172d18890 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -963,7 +963,10 @@ void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
     if (Param.hasStructRetAttr()) {
       SRetOffset = 1;
       llvm::Type *VarType = Param.getParamStructRetType();
-      llvm::Value *Var = B.CreateAlloca(VarType);
+      llvm::Value *Var =
+          CGM.getLangOpts().EmitLogicalPointer
+              ? cast<Instruction>(B.CreateStructuredAlloca(VarType))
+              : cast<Instruction>(B.CreateAlloca(VarType));
       OutputSemantic.push_back(std::make_pair(Var, VarType));
       Args.push_back(Var);
       continue;
@@ -986,7 +989,11 @@ void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
       if (!SemanticValue)
         return;
       if (Param.hasByValAttr()) {
-        llvm::Value *Var = B.CreateAlloca(Param.getParamByValType());
+        llvm::Value *Var =
+            CGM.getLangOpts().EmitLogicalPointer
+                ? cast<Instruction>(
+                      B.CreateStructuredAlloca(Param.getParamByValType()))
+                : cast<Instruction>(B.CreateAlloca(Param.getParamByValType()));
         B.CreateStore(SemanticValue, Var);
         SemanticValue = Var;
       }
@@ -1431,7 +1438,7 @@ std::optional<LValue> CGHLSLRuntime::emitBufferArraySubscriptExpr(
   Indices.push_back(Idx);
   Indices.push_back(llvm::ConstantInt::get(CGF.Int32Ty, 0));
 
-  if (CGF.getLangOpts().EmitStructuredGEP) {
+  if (CGF.getLangOpts().EmitLogicalPointer) {
     // 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());
@@ -1646,7 +1653,7 @@ LValue CGHLSLRuntime::emitBufferMemberExpr(CodeGenFunction &CGF,
   CharUnits Align = CharUnits::fromQuantity(
       CGF.CGM.getDataLayout().getABITypeAlign(FieldLLVMTy));
 
-  Value *Ptr = CGF.getLangOpts().EmitStructuredGEP
+  Value *Ptr = CGF.getLangOpts().EmitLogicalPointer
                    ? CGF.Builder.CreateStructuredGEP(
                          LayoutTy, Base.getPointer(CGF),
                          llvm::ConstantInt::get(CGM.IntTy, FieldIdx))
diff --git a/clang/test/CodeGenHLSL/sgep/array_load.hlsl b/clang/test/CodeGenHLSL/sgep/array_load.hlsl
index fa2feca1ae961..db3d5a1580023 100644
--- a/clang/test/CodeGenHLSL/sgep/array_load.hlsl
+++ b/clang/test/CodeGenHLSL/sgep/array_load.hlsl
@@ -1,8 +1,8 @@
-// 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
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -fexperimental-logical-pointer -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-logical-pointer -o - %s | FileCheck %s
 
 void foo() {
-// CHECK: %array = alloca [3 x i32], align 4
+// CHECK: %array = call elementtype([3 x i32]) ptr @llvm.structured.alloca.p0()
   uint array[3] = { 0, 1, 2 };
 
 // CHECK: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x i32]) %array, {{i32|i64}} 2)
@@ -16,7 +16,7 @@ struct S {
 };
 
 void bar() {
-// CHECK: %array = alloca [3 x %struct.S], align 1
+// CHECK: %array = call elementtype([3 x %struct.S]) ptr @llvm.structured.alloca.p0()
   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)
@@ -32,7 +32,7 @@ struct S2 {
 };
 
 void baz() {
-// CHECK: %array = alloca [2 x %struct.S2], align 1
+// CHECK: %array = call elementtype([2 x %struct.S2]) ptr @llvm.structured.alloca.p0()
   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)
diff --git a/clang/test/CodeGenHLSL/sgep/array_store.hlsl b/clang/test/CodeGenHLSL/sgep/array_store.hlsl
index f08d2b1f5d258..886bc49c0019e 100644
--- a/clang/test/CodeGenHLSL/sgep/array_store.hlsl
+++ b/clang/test/CodeGenHLSL/sgep/array_store.hlsl
@@ -1,10 +1,10 @@
-// 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
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -fexperimental-logical-pointer -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-logical-pointer -o - %s | FileCheck %s
 
 [shader("compute")]
 [numthreads(1,1,1)]
 void foo() {
-// CHECK: %array = alloca [10 x i32], align 4
+// CHECK: %array = call elementtype([10 x i32]) ptr @llvm.structured.alloca.p0()
   uint array[10];
 
 // CHECK: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %array, {{i32|i64}} 2)
@@ -18,7 +18,7 @@ struct S {
 };
 
 void bar() {
-// CHECK: %array = alloca [3 x %struct.S], align 1
+// CHECK: %array = call elementtype([3 x %struct.S]) ptr @llvm.structured.alloca.p0()
   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)
@@ -35,7 +35,7 @@ struct S2 {
 };
 
 void baz() {
-// CHECK: %array = alloca [2 x %struct.S2], align 1
+// CHECK: %array = call elementtype([2 x %struct.S2]) ptr @llvm.structured.alloca.p0()
   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)
diff --git a/clang/test/CodeGenHLSL/sgep/load_global.hlsl b/clang/test/CodeGenHLSL/sgep/load_global.hlsl
index 18e57e04e64fc..b35583591fddf 100644
--- a/clang/test/CodeGenHLSL/sgep/load_global.hlsl
+++ b/clang/test/CodeGenHLSL/sgep/load_global.hlsl
@@ -1,5 +1,5 @@
-// 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
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -fexperimental-logical-pointer -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-logical-pointer -o - %s | FileCheck %s --check-prefixes=CHECK-SPIR
 
 struct S {
   uint a;
diff --git a/clang/test/CodeGenHLSL/sgep/object_method.hlsl b/clang/test/CodeGenHLSL/sgep/object_method.hlsl
index fae271e26d0f9..34b6c4a44ed65 100644
--- a/clang/test/CodeGenHLSL/sgep/object_method.hlsl
+++ b/clang/test/CodeGenHLSL/sgep/object_method.hlsl
@@ -1,5 +1,5 @@
-// 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
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm -finclude-default-header -fexperimental-logical-pointer -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-logical-pointer -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-SPIR
 
 struct O {
   int value = 0;
@@ -14,7 +14,7 @@ struct O {
 void foo() {
   O o;
 
-// CHECK:      %o = alloca %struct.O, align 1
+// CHECK:      %o = call elementtype(%struct.O) ptr @llvm.structured.alloca.p0()
 // 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();
diff --git a/llvm/include/llvm/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h
index 4ed3d73c4a057..41e4b295da5f2 100644
--- a/llvm/include/llvm/IR/IRBuilder.h
+++ b/llvm/include/llvm/IR/IRBuilder.h
@@ -1872,6 +1872,16 @@ class IRBuilderBase {
     return Insert(new AllocaInst(Ty, AddrSpace, ArraySize, AllocaAlign), Name);
   }
 
+  CallInst *CreateStructuredAlloca(Type *BaseType, const Twine &Name = "") {
+    const DataLayout &DL = BB->getDataLayout();
+    PointerType *PtrTy = DL.getAllocaPtrType(Context);
+    CallInst *Output =
+        CreateIntrinsic(Intrinsic::structured_alloca, {PtrTy}, {}, {}, Name);
+    Output->addRetAttr(
+        Attribute::get(getContext(), Attribute::ElementType, BaseType));
+    return Output;
+  }
+
   /// Provided to resolve 'CreateLoad(Ty, Ptr, "...")' correctly, instead of
   /// converting the string to 'bool' for the isVolatile parameter.
   LoadInst *CreateLoad(Type *Ty, Value *Ptr, const char *Name) {



More information about the llvm-commits mailing list