https://github.com/Keenuts updated https://github.com/llvm/llvm-project/pull/176145
From 9cf4186960bad774bba627984d93cd730207bcb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Thu, 13 Nov 2025 15:34:21 +0100 Subject: [PATCH 01/16] [IR][HLSL] Add llvm.structured.gep instruction This commit adds initial support for @llvm.structured.gep instruction in Clang. This intrinsic is supposed to be used as an alternative to ptrdiff/GEP when pointers arithmetic is invalid and only structured accesss is possible. Adding this new instruction will require frontend and backend support, and will require many changes. To correctly stage those changes, I decided to gate the SGEP emission behind a compiler flag: `-fexperimental-emit-sgep` If this flag is enabled, the GEP instruction will be replaced by the FE into the @llvm.structured.gep. The goal is not to keep this, as HLSL should in the end only emit SGEP intrinsic, but to stage the FE & BE changes required to support this intrinsic. Link to the RFC: https://discourse.llvm.org/t/rfc-adding-instructions-to-to-carry-gep-type-traversal-information/ Previous discussion around the documentation: https://github.com/llvm/llvm-project/pull/167883/files --- clang/include/clang/Basic/LangOptions.def | 1 + clang/include/clang/Options/Options.td | 7 + clang/lib/CodeGen/CGExpr.cpp | 31 +++ clang/lib/CodeGen/CGHLSLRuntime.cpp | 33 +++- clang/test/CodeGenHLSL/sgep/array_load.hlsl | 52 +++++ clang/test/CodeGenHLSL/sgep/array_store.hlsl | 53 +++++ clang/test/CodeGenHLSL/sgep/load_global.hlsl | 44 +++++ .../test/CodeGenHLSL/sgep/object_method.hlsl | 27 +++ llvm/docs/LangRef.rst | 182 ++++++++++++++++++ llvm/include/llvm/IR/IRBuilder.h | 14 ++ llvm/include/llvm/IR/IntrinsicInst.h | 38 ++++ llvm/include/llvm/IR/Intrinsics.td | 5 + 12 files changed, 480 insertions(+), 7 deletions(-) create mode 100644 clang/test/CodeGenHLSL/sgep/array_load.hlsl create mode 100644 clang/test/CodeGenHLSL/sgep/array_store.hlsl create mode 100644 clang/test/CodeGenHLSL/sgep/load_global.hlsl create mode 100644 clang/test/CodeGenHLSL/sgep/object_method.hlsl diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 8cba1dbaee24e..dcae56e4261ec 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -248,6 +248,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 instructions instead of GEP") 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 d4dd71b9d1bea..b98dbaef7c5f6 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -9792,6 +9792,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 896c60b13c160..541ca00191d93 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -4468,6 +4468,17 @@ Address CodeGenFunction::EmitArrayToPointerDecay(const Expr *E, if (!E->getType()->isVariableArrayType()) { assert(isa<llvm::ArrayType>(Addr.getElementType()) && "Expected pointer to array"); + + if (getLangOpts().EmitStructuredGEP) { + llvm::Value *Ptr = Addr.emitRawPointer(*this); + if (auto *C = dyn_cast<llvm::Constant>(Ptr)) + return Address(C, Addr.getElementType(), Addr.getAlignment(), + Addr.isKnownNonNull()); + return Address( + Builder.CreateStructuredGEP(NewTy, Addr.getBasePointer(), {}), + Addr.getElementType(), Addr.getAlignment(), Addr.isKnownNonNull()); + } + Addr = Builder.CreateConstArrayGEP(Addr, 0, "arraydecay"); } @@ -4507,6 +4518,9 @@ static llvm::Value *emitArraySubscriptGEP(CodeGenFunction &CGF, bool signedIndices, SourceLocation loc, const llvm::Twine &name = "arrayidx") { + if (CGF.getLangOpts().EmitStructuredGEP) + return CGF.Builder.CreateStructuredGEP(elemType, ptr, indices); + if (inbounds) { return CGF.EmitCheckedInBoundsGEP(elemType, ptr, indices, signedIndices, CodeGenFunction::NotSubtraction, loc, @@ -4518,10 +4532,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 (arrayType && 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, @@ -4638,6 +4659,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; @@ -5492,6 +5515,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 e796bbb5855c8..e2230952cc85e 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -1113,7 +1113,12 @@ static void initializeBuffer(CodeGenModule &CGM, llvm::GlobalVariable *GV, /*ReturnType=*/HandleTy, IntrID, Args, nullptr, Twine(GV->getName()).concat("_h")); - llvm::Value *HandleRef = Builder.CreateStructGEP(GV->getValueType(), GV, 0); + llvm::Value *HandleRef = + CGM.getLangOpts().EmitStructuredGEP + ? Builder.CreateStructuredGEP(GV->getValueType(), GV, + llvm::ConstantInt::get(CGM.IntTy, 0)) + : Builder.CreateStructGEP(GV->getValueType(), GV, 0); + Builder.CreateAlignedStore(CreateHandle, HandleRef, HandleRef->getPointerAlignment(DL)); Builder.CreateRetVoid(); @@ -1397,9 +1402,18 @@ std::optional<LValue> CGHLSLRuntime::emitBufferArraySubscriptExpr( Indices.push_back(Idx); Indices.push_back(llvm::ConstantInt::get(CGF.Int32Ty, 0)); - llvm::Value *GEP = CGF.Builder.CreateGEP(LayoutTy, Addr.emitRawPointer(CGF), - Indices, "cbufferidx"); - Addr = Address(GEP, Addr.getElementType(), RowAlignedSize, KnownNonNull); + if (CGF.getLangOpts().EmitStructuredGEP) { + if (auto *AT = dyn_cast<llvm::ArrayType>(Addr.getElementType())) + LayoutTy = llvm::ArrayType::get(LayoutTy, AT->getNumElements()); + auto *GEP = CGF.Builder.CreateStructuredGEP( + LayoutTy, Addr.emitRawPointer(CGF), Indices, "cbufferidx"); + Addr = + Address(GEP, GEP->getResultElementType(), RowAlignedSize, KnownNonNull); + } else { + 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); } @@ -1579,9 +1593,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..5e78ee1e8a557 --- /dev/null +++ b/clang/test/CodeGenHLSL/sgep/array_load.hlsl @@ -0,0 +1,52 @@ +// 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 + +void foo() { +// CHECK: %array = alloca [3 x i32], align 4 + uint array[3] = { 0, 1, 2 }; + +// CHECK-DXIL: %[[#PTR:]] = call ptr ([3 x i32], ptr, ...) @llvm.structured.gep.p0.a3i32([3 x i32] poison, ptr %array, i32 2) +// CHECK-SPIR: %[[#PTR:]] = call ptr ([3 x i32], ptr, ...) @llvm.structured.gep.p0.a3i32([3 x i32] poison, ptr %array, 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-DXIL: %[[#A:]] = call ptr ([3 x %struct.S], ptr, ...) @llvm.structured.gep.p0.a3s_struct.Ss([3 x %struct.S] poison, ptr %array, i32 2) +// CHECK-DXIL: %[[#B:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#A]], i32 1) +// CHECK-SPIR: %[[#A:]] = call ptr ([3 x %struct.S], ptr, ...) @llvm.structured.gep.p0.a3s_struct.Ss([3 x %struct.S] poison, ptr %array, i64 2) +// CHECK-SPIR: %[[#B:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#A]], 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-DXIL: %[[#A:]] = call ptr ([2 x %struct.S2], ptr, ...) @llvm.structured.gep.p0.a2s_struct.S2s([2 x %struct.S2] poison, ptr %array, i32 1) +// CHECK-DXIL: %[[#B:]] = call ptr (%struct.S2, ptr, ...) @llvm.structured.gep.p0.s_struct.S2s(%struct.S2 poison, ptr %[[#A]], i32 1) +// CHECK-DXIL: %[[#C:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#B]], i32 0) + +// CHECK-SPIR: %[[#A:]] = call ptr ([2 x %struct.S2], ptr, ...) @llvm.structured.gep.p0.a2s_struct.S2s([2 x %struct.S2] poison, ptr %array, i64 1) +// CHECK-SPIR: %[[#B:]] = call ptr (%struct.S2, ptr, ...) @llvm.structured.gep.p0.s_struct.S2s(%struct.S2 poison, ptr %[[#A]], i64 1) +// CHECK-SPIR: %[[#C:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#B]], 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..756dc9f960602 --- /dev/null +++ b/clang/test/CodeGenHLSL/sgep/array_store.hlsl @@ -0,0 +1,53 @@ +// 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 + +[shader("compute")] +[numthreads(1,1,1)] +void foo() { +// CHECK: %array = alloca [10 x i32], align 4 + uint array[10]; + +// CHECK-DXIL: %[[#PTR:]] = call ptr ([10 x i32], ptr, ...) @llvm.structured.gep.p0.a10i32([10 x i32] poison, ptr %array, i32 2) +// CHECK-SPIR: %[[#PTR:]] = call ptr ([10 x i32], ptr, ...) @llvm.structured.gep.p0.a10i32([10 x i32] poison, ptr %array, 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-DXIL: %[[#A:]] = call ptr ([3 x %struct.S], ptr, ...) @llvm.structured.gep.p0.a3s_struct.Ss([3 x %struct.S] poison, ptr %array, i32 2) +// CHECK-DXIL: %[[#B:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#A]], i32 1) +// CHECK-SPIR: %[[#A:]] = call ptr ([3 x %struct.S], ptr, ...) @llvm.structured.gep.p0.a3s_struct.Ss([3 x %struct.S] poison, ptr %array, i64 2) +// CHECK-SPIR: %[[#B:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#A]], 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-DXIL: %[[#A:]] = call ptr ([2 x %struct.S2], ptr, ...) @llvm.structured.gep.p0.a2s_struct.S2s([2 x %struct.S2] poison, ptr %array, i32 1) +// CHECK-DXIL: %[[#B:]] = call ptr (%struct.S2, ptr, ...) @llvm.structured.gep.p0.s_struct.S2s(%struct.S2 poison, ptr %[[#A]], i32 1) +// CHECK-DXIL: %[[#C:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#B]], i32 0) + +// CHECK-SPIR: %[[#A:]] = call ptr ([2 x %struct.S2], ptr, ...) @llvm.structured.gep.p0.a2s_struct.S2s([2 x %struct.S2] poison, ptr %array, i64 1) +// CHECK-SPIR: %[[#B:]] = call ptr (%struct.S2, ptr, ...) @llvm.structured.gep.p0.s_struct.S2s(%struct.S2 poison, ptr %[[#A]], i64 1) +// CHECK-SPIR: %[[#C:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#B]], 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..cd6a602c99c16 --- /dev/null +++ b/clang/test/CodeGenHLSL/sgep/load_global.hlsl @@ -0,0 +1,44 @@ +// 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) (%S, ptr addrspace(2), ...) @llvm.structured.gep.p2.s_Ss(%S poison, ptr addrspace(2) @_ZL1s, i32 1) +// CHECK-DXIL: %[[#]] = load i32, ptr addrspace(2) %[[#PTR]], align 4 + +// CHECK-SPIR: %[[#PTR:]] = call ptr addrspace(12) (%S, ptr addrspace(12), ...) @llvm.structured.gep.p12.s_Ss(%S poison, ptr addrspace(12) @_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) ([4 x <{ i32, target("dx.Padding", 12) }>], ptr addrspace(2), ...) @llvm.structured.gep.p2.a4sl_i32tdx.Padding_12ts([4 x <{ i32, target("dx.Padding", 12) }>] poison, ptr addrspace(2) @_ZL1a, i32 2, i32 0) +// CHECK-DXIL: %[[#]] = load i32, ptr addrspace(2) %cbufferidx, align 16 + +// CHECK-SPIR: %cbufferidx = call ptr addrspace(12) ([4 x <{ i32, target("spirv.Padding", 12) }>], ptr addrspace(12), ...) @llvm.structured.gep.p12.a4sl_i32tspirv.Padding_12ts([4 x <{ i32, target("spirv.Padding", 12) }>] poison, ptr addrspace(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..42fb5725f008d --- /dev/null +++ b/clang/test/CodeGenHLSL/sgep/object_method.hlsl @@ -0,0 +1,27 @@ +// 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-DXIL: %[[#A:]] = call ptr (%struct.O, ptr, ...) @llvm.structured.gep.p0.s_struct.Os(%struct.O poison, ptr %this1, i32 0) +// CHECK-SPIR: %[[#A:]] = call ptr (%struct.O, ptr, ...) @llvm.structured.gep.p0.s_struct.Os(%struct.O poison, ptr %this1, i64 0) +// CHECK: %[[#B:]] = load i32, ptr %[[#A]], align 1 diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index a6881099f81f4..26ff48b794e05 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -14991,6 +14991,188 @@ Semantics: See the description for :ref:`llvm.stacksave <int_stacksave>`. +.. _i_structured_gep: + +'``llvm.structured.gep``' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare <ret_type> + @llvm.structured.gep(ptr elementtype(<basetype>) <source> + {, [i32/i64] <index> }*) + +Overview: +""""""""" + +The '``llvm.structured.gep``' intrinsic (structured **G**\ et\ **E**\ lement\ **P**\ tr) computes a new pointer address +resulting of a logical indexing into the ``<source>`` pointer. The returned +address depends on the indices and may depend on the layout of %basetype at +runtime. + +Arguments: +"""""""""" + +``<ty> basetype``: +The actual value passed is ignored, and should be ``poison``. + +``ptr <source>``: +A pointer to a memory location assumed to hold a completely laid out value +with the same type as ``basetype``. The physical layout of ``basetype`` is +target dependent, and is not always known at compile time. +The assumption this instruction makes on the memory location is only relevant +to this particular call. A frontend could possibly emit multiple structured +GEP with the same source pointer but a different ``basetype``. + +The ``source`` argument must be annotated with an :ref:`elementtype +<attr_elementtype>` attribute at the call-site. This attribute specifies the +type of the element pointed by the pointer source. This type will be +used along with the provided indices and source operands to compute a new +pointer representing the result of a logical indexing into a basetype +pointed by source. + +``[i32/i64] index, ...``: +Indices used to traverse into the basetype and determine the target element +this instruction computes an offset for. Indices can be 32-bit or 64-bit +unsigned integers. Indices being handled one by one, both sizes can be mixed +in the same instruction. The precision used to compute the resulting pointer +is target-dependent. +When used to index into a struct, only integer constants are allowed. + +Semantics: +"""""""""" + +The ``llvm.structured.gep`` performs a logical traversal of the type +``basetype`` using the list of provided indices, computing the pointer +addressing the targeted element/field assuming ``source`` points to a +physically laid out ``basetype``. + +The first index determines which element/field of ``basetype`` is selected, +computes the pointer to access this element/field assuming ``source`` points +to the start of ``basetype``. +This pointer becomes the new ``source``, the current type the new +``basetype``, and the next indices is consumed until a scalar type is +reached or all indices are consumed. + +All indices must be consumed, and it is illegal to index into a scalar type. +Meaning the maximum number of indices depends on the depth of the basetype. + +Because this instruction performs a logical addressing, all indices are +assumed to be inbounds. This means it is not possible to access the next +element in the logical layout by overflowing: + +- If the indexed type is a struct with N fields, the index must be an + immediate/constant value in the range ``[0; N[``. +- If indexing into an array or vector, the index can be a variable, but + assumed to be inbounds with regards to the current basetype logical layout. +- If the traversed type is an array or vector of N elements with ``N > 0``, + the index is assumed to belong to ``[0; N[``. +- If the traversed type is an array of size ``0``, the array size is assumed + to be known at runtime, and the instruction assumes the index is always + inbound. + +If the source pointer is poison, the instruction returns poison. +The resulting pointer belongs to the same address space as ``source``. + +This instruction assumes the pointer ``source`` points to a valid memory +location large enough to contain the physically laid out version ``basetype``. +This instruction does not dereference any pointer, but requires the source +operand to be a valid memory location. Meaning this instruction cannot be +used as an ``offsetof`` by providing ``ptr 0`` as source. +If the memory location pointed by source is not large enough, using the +resulting pointer to access memory yields an undefined behavior. + +Example: +"""""""" + +**Simple case: logical access of a struct field** + +.. code-block:: cpp + + struct A { int a, int b, int c, int d }; + int val = my_struct->b; + +Could be translated to: + +.. code-block:: llvm + + %A = type { i32, i32, i32, i32 } + %src = call ptr @llvm.structured.gep(%A poison, ptr %my_struct, i32 1) + %val = load i32, ptr %src + +**A more complex case** + +This instruction can also be used on the same pointer with different +basetypes, as long as codegen knows how those are physically laid out. +Let’s consider the following code: + +.. code-block:: cpp + + struct S { + uint3 array[5]; + float my_float; + } + + my_struct->array[2].y = 12; + int val = my_struct->array[index].y; + + +The frontend knows this struct has a particular physical layout: + - an array of 5 vectors of 3 integers, aligned on 16 bytes. + - one float at the end, with no alignment requirement, so packed right + after the last ``uint3``, which is a ``<3 x uint>``. + +.. code-block:: llvm + + %S = type { [4 x { <3 x i32>, i32 }, <3 x i32>, float } + ; `-> explicit padding + ; -> 4 byte padding between each array element, except + ; between the last vector and the float. + +The store is simple: + +.. code-block:: llvm + + %dst = call ptr @llvm.structured.gep(%S poison, ptr %my_struct, i32 0, i32 2, i32 0, i32 1) + store i32 12, ptr %dst + +But the load depends on a dynamic index. This means accessing logically the +first 4 elements is different than accessing the last: + +.. code-block:: llvm + + %firsts = call ptr @llvm.structured.gep(%S poison, ptr %my_struct, i32 0, i32 %index, i32 0) + %last = call ptr @llvm.structured.gep(%S poison, ptr %my_struct, i32 1) + +And because :ref:`i_structured_gep` always assumes indexing inbounds, we +cannot reach the last element by doing: + +.. code-block:: llvm + + ; BAD + call ptr @llvm.structured.gep(%S poison, ptr %my_struct, i32 0, i32 5, i32 0) + +If codegen knew nothing about the physical layout of ``%S``, a condition +would be required to select between ``%firsts`` and ``%last`` depending on +the value of ``%index``. +But in our case, the codegen knows that some logical layouts are equivalent +to others (even if the physical layout is unknown). +In this context, it knows that the following type can be used to logically +address every vector in the array, generating the following code: + +.. code-block:: llvm + + %T = type [ 5 x { <3 x i32>, i32 } ] + %ptr = call ptr @llvm.structured.gep(%T poison, ptr %my_struct, i32 %index, i32 0, i32 1) + store i32 12, ptr %ptr + +This is, however, dependent on context that codegen has an insight on. This +logical layout equivalence is not a generic rule. + + .. _int_get_dynamic_area_offset: '``llvm.get.dynamic.area.offset``' Intrinsic diff --git a/llvm/include/llvm/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h index 8c33f6efbe742..d2f23dc8c1baf 100644 --- a/llvm/include/llvm/IR/IRBuilder.h +++ b/llvm/include/llvm/IR/IRBuilder.h @@ -32,6 +32,7 @@ #include "llvm/IR/InstrTypes.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Operator.h" @@ -1923,6 +1924,19 @@ class IRBuilderBase { return Insert(new AtomicRMWInst(Op, Ptr, Val, *Align, Ordering, SSID)); } + StructuredGEPInst *CreateStructuredGEP(Type *BaseType, Value *PtrBase, + ArrayRef<llvm::Value *> Indices, + const Twine &Name = "") { + SmallVector<llvm::Value *, 4> Args; + Args.push_back(llvm::PoisonValue::get(BaseType)); + Args.push_back(PtrBase); + llvm::append_range(Args, Indices); + + return cast<StructuredGEPInst>( + CreateIntrinsic(Intrinsic::structured_gep, + {PtrBase->getType(), BaseType}, Args, {}, Name)); + } + Value *CreateGEP(Type *Ty, Value *Ptr, ArrayRef<Value *> IdxList, const Twine &Name = "", GEPNoWrapFlags NW = GEPNoWrapFlags::none()) { diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h index 0b25baa465a71..823db889dfae6 100644 --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -1804,6 +1804,44 @@ class ConvergenceControlInst : public IntrinsicInst { CreateLoop(BasicBlock &BB, ConvergenceControlInst *Parent); }; +class StructuredGEPInst : public IntrinsicInst { +public: + static bool classof(const IntrinsicInst *I) { + return I->getIntrinsicID() == Intrinsic::structured_gep; + } + + static bool classof(const Value *V) { + return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V)); + } + + Value *getPointerOperand() const { return getOperand(1); } + + Type *getBaseType() const { return getOperand(0)->getType(); } + + unsigned getIndicesCount() const { return getNumOperands() - 3; } + + Type *getResultElementType() const { + Type *CurrentType = getBaseType(); + for (unsigned I = 0; I < getIndicesCount(); I++) { + Value *V = getOperand(I + 2); + ConstantInt *CI = dyn_cast<ConstantInt>(V); + if (!CI) + return nullptr; + + if (ArrayType *AT = dyn_cast<ArrayType>(CurrentType)) { + CurrentType = AT->getElementType(); + } else if (StructType *ST = dyn_cast<StructType>(CurrentType)) { + CurrentType = ST->getElementType(CI->getZExtValue()); + } else { + // FIXME? + assert(0); + } + } + + return CurrentType; + } +}; + } // end namespace llvm #endif // LLVM_IR_INTRINSICINST_H diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td index 24d51bdaf67fb..13d222edc5774 100644 --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -1029,6 +1029,11 @@ def int_call_preallocated_teardown : DefaultAttrsIntrinsic<[], [llvm_token_ty]>; def int_callbr_landingpad : Intrinsic<[llvm_any_ty], [LLVMMatchType<0>], [IntrNoMerge]>; +def int_structured_gep + : Intrinsic<[llvm_anyptr_ty], + [llvm_any_ty, LLVMMatchType<0>, llvm_vararg_ty], + [IntrNoMem, IntrWillReturn]>; + //===------------------- Standard C Library Intrinsics --------------------===// // From a3f89fe6db93387e8b7ab4f1f5a4ac49559805da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Thu, 15 Jan 2026 13:22:46 +0100 Subject: [PATCH 02/16] comment FIXME --- llvm/include/llvm/IR/IntrinsicInst.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h index 823db889dfae6..1232345f5ddfb 100644 --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -1833,7 +1833,7 @@ class StructuredGEPInst : public IntrinsicInst { } else if (StructType *ST = dyn_cast<StructType>(CurrentType)) { CurrentType = ST->getElementType(CI->getZExtValue()); } else { - // FIXME? + // FIXME(Keenuts): add testing reaching those places once initial implementation has landed. assert(0); } } From d9acda614c58248da164edd01f4429f8689420ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Thu, 15 Jan 2026 13:29:57 +0100 Subject: [PATCH 03/16] clang-format --- llvm/include/llvm/IR/IntrinsicInst.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h index 1232345f5ddfb..f99c3dac683b4 100644 --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -1833,7 +1833,8 @@ class StructuredGEPInst : public IntrinsicInst { } else if (StructType *ST = dyn_cast<StructType>(CurrentType)) { CurrentType = ST->getElementType(CI->getZExtValue()); } else { - // FIXME(Keenuts): add testing reaching those places once initial implementation has landed. + // FIXME(Keenuts): add testing reaching those places once initial + // implementation has landed. assert(0); } } From 907e8777c205f9c129e021658f1ebbc04eca4fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Thu, 15 Jan 2026 15:43:21 +0100 Subject: [PATCH 04/16] change to ElementTYpe attr --- clang/test/CodeGenHLSL/sgep/array_load.hlsl | 24 ++++++++--------- clang/test/CodeGenHLSL/sgep/array_store.hlsl | 26 ++++++++++--------- clang/test/CodeGenHLSL/sgep/load_global.hlsl | 9 ++++--- .../test/CodeGenHLSL/sgep/object_method.hlsl | 7 +++-- llvm/include/llvm/IR/IRBuilder.h | 9 ++++--- llvm/include/llvm/IR/IntrinsicInst.h | 8 +++--- llvm/include/llvm/IR/Intrinsics.td | 5 ++-- 7 files changed, 48 insertions(+), 40 deletions(-) diff --git a/clang/test/CodeGenHLSL/sgep/array_load.hlsl b/clang/test/CodeGenHLSL/sgep/array_load.hlsl index 5e78ee1e8a557..26efa7cef57dc 100644 --- a/clang/test/CodeGenHLSL/sgep/array_load.hlsl +++ b/clang/test/CodeGenHLSL/sgep/array_load.hlsl @@ -5,8 +5,8 @@ void foo() { // CHECK: %array = alloca [3 x i32], align 4 uint array[3] = { 0, 1, 2 }; -// CHECK-DXIL: %[[#PTR:]] = call ptr ([3 x i32], ptr, ...) @llvm.structured.gep.p0.a3i32([3 x i32] poison, ptr %array, i32 2) -// CHECK-SPIR: %[[#PTR:]] = call ptr ([3 x i32], ptr, ...) @llvm.structured.gep.p0.a3i32([3 x i32] poison, ptr %array, i64 2) +// CHECK-DXIL: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x i32]) %array, i32 2) +// CHECK-SPIR: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x i32]) %array, i64 2) // CHECK: load i32, ptr %[[#PTR]], align 4 uint tmp = array[2]; } @@ -20,10 +20,10 @@ void bar() { // CHECK: %array = alloca [3 x %struct.S], align 1 S array[3] = { { 0, 1 }, { 2, 3 }, { 3, 4 } }; -// CHECK-DXIL: %[[#A:]] = call ptr ([3 x %struct.S], ptr, ...) @llvm.structured.gep.p0.a3s_struct.Ss([3 x %struct.S] poison, ptr %array, i32 2) -// CHECK-DXIL: %[[#B:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#A]], i32 1) -// CHECK-SPIR: %[[#A:]] = call ptr ([3 x %struct.S], ptr, ...) @llvm.structured.gep.p0.a3s_struct.Ss([3 x %struct.S] poison, ptr %array, i64 2) -// CHECK-SPIR: %[[#B:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#A]], i64 1) +// CHECK-DXIL: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %array, i32 2) +// CHECK-DXIL: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#A]], i32 1) +// CHECK-SPIR: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %array, i64 2) +// CHECK-SPIR: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#A]], i64 1) // CHECK: load i32, ptr %[[#B]], align 1 uint tmp = array[2].b; @@ -39,13 +39,13 @@ void baz() { // CHECK: %array = alloca [2 x %struct.S2], align 1 S2 array[2] = { { 0, { 1, 2 }, 3 }, { 4, { 5, 6 }, 7 } }; -// CHECK-DXIL: %[[#A:]] = call ptr ([2 x %struct.S2], ptr, ...) @llvm.structured.gep.p0.a2s_struct.S2s([2 x %struct.S2] poison, ptr %array, i32 1) -// CHECK-DXIL: %[[#B:]] = call ptr (%struct.S2, ptr, ...) @llvm.structured.gep.p0.s_struct.S2s(%struct.S2 poison, ptr %[[#A]], i32 1) -// CHECK-DXIL: %[[#C:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#B]], i32 0) +// CHECK-DXIL: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([2 x %struct.S2]) %array, i32 1) +// CHECK-DXIL: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %[[#A]], i32 1) +// CHECK-DXIL: %[[#C:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#B]], i32 0) -// CHECK-SPIR: %[[#A:]] = call ptr ([2 x %struct.S2], ptr, ...) @llvm.structured.gep.p0.a2s_struct.S2s([2 x %struct.S2] poison, ptr %array, i64 1) -// CHECK-SPIR: %[[#B:]] = call ptr (%struct.S2, ptr, ...) @llvm.structured.gep.p0.s_struct.S2s(%struct.S2 poison, ptr %[[#A]], i64 1) -// CHECK-SPIR: %[[#C:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#B]], i64 0) +// CHECK-SPIR: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([2 x %struct.S2]) %array, i64 1) +// CHECK-SPIR: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %[[#A]], i64 1) +// CHECK-SPIR: %[[#C:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#B]], 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 index 756dc9f960602..a798bc47da50c 100644 --- a/clang/test/CodeGenHLSL/sgep/array_store.hlsl +++ b/clang/test/CodeGenHLSL/sgep/array_store.hlsl @@ -7,8 +7,8 @@ void foo() { // CHECK: %array = alloca [10 x i32], align 4 uint array[10]; -// CHECK-DXIL: %[[#PTR:]] = call ptr ([10 x i32], ptr, ...) @llvm.structured.gep.p0.a10i32([10 x i32] poison, ptr %array, i32 2) -// CHECK-SPIR: %[[#PTR:]] = call ptr ([10 x i32], ptr, ...) @llvm.structured.gep.p0.a10i32([10 x i32] poison, ptr %array, i64 2) +// CHECK-DXIL: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %array, i32 2) +// CHECK-SPIR: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %array, i64 2) // CHECK: store i32 10, ptr %[[#PTR]], align 4 array[2] = 10; } @@ -22,10 +22,12 @@ void bar() { // CHECK: %array = alloca [3 x %struct.S], align 1 S array[3] = { { 0, 1 }, { 2, 3 }, { 3, 4 } }; -// CHECK-DXIL: %[[#A:]] = call ptr ([3 x %struct.S], ptr, ...) @llvm.structured.gep.p0.a3s_struct.Ss([3 x %struct.S] poison, ptr %array, i32 2) -// CHECK-DXIL: %[[#B:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#A]], i32 1) -// CHECK-SPIR: %[[#A:]] = call ptr ([3 x %struct.S], ptr, ...) @llvm.structured.gep.p0.a3s_struct.Ss([3 x %struct.S] poison, ptr %array, i64 2) -// CHECK-SPIR: %[[#B:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#A]], i64 1) +// CHECK-DXIL: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %array, i32 2) +// CHECK-DXIL: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#A]], i32 1) + +// CHECK-SPIR: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %array, i64 2) +// CHECK-SPIR: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#A]], i64 1) + // CHECK: store i32 10, ptr %[[#B]], align 1 array[2].b = 10; } @@ -40,13 +42,13 @@ void baz() { // CHECK: %array = alloca [2 x %struct.S2], align 1 S2 array[2] = { { 0, { 1, 2 }, 3 }, { 4, { 5, 6 }, 7 } }; -// CHECK-DXIL: %[[#A:]] = call ptr ([2 x %struct.S2], ptr, ...) @llvm.structured.gep.p0.a2s_struct.S2s([2 x %struct.S2] poison, ptr %array, i32 1) -// CHECK-DXIL: %[[#B:]] = call ptr (%struct.S2, ptr, ...) @llvm.structured.gep.p0.s_struct.S2s(%struct.S2 poison, ptr %[[#A]], i32 1) -// CHECK-DXIL: %[[#C:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#B]], i32 0) +// CHECK-DXIL: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([2 x %struct.S2]) %array, i32 1) +// CHECK-DXIL: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %[[#A]], i32 1) +// CHECK-DXIL: %[[#C:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#B]], i32 0) -// CHECK-SPIR: %[[#A:]] = call ptr ([2 x %struct.S2], ptr, ...) @llvm.structured.gep.p0.a2s_struct.S2s([2 x %struct.S2] poison, ptr %array, i64 1) -// CHECK-SPIR: %[[#B:]] = call ptr (%struct.S2, ptr, ...) @llvm.structured.gep.p0.s_struct.S2s(%struct.S2 poison, ptr %[[#A]], i64 1) -// CHECK-SPIR: %[[#C:]] = call ptr (%struct.S, ptr, ...) @llvm.structured.gep.p0.s_struct.Ss(%struct.S poison, ptr %[[#B]], i64 0) +// CHECK-SPIR: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([2 x %struct.S2]) %array, i64 1) +// CHECK-SPIR: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %[[#A]], i64 1) +// CHECK-SPIR: %[[#C:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#B]], 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 index cd6a602c99c16..18e57e04e64fc 100644 --- a/clang/test/CodeGenHLSL/sgep/load_global.hlsl +++ b/clang/test/CodeGenHLSL/sgep/load_global.hlsl @@ -20,19 +20,20 @@ const uint a[4]; const uint b; void foo() { -// CHECK-DXIL: %[[#PTR:]] = call ptr addrspace(2) (%S, ptr addrspace(2), ...) @llvm.structured.gep.p2.s_Ss(%S poison, ptr addrspace(2) @_ZL1s, i32 1) + +// 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) (%S, ptr addrspace(12), ...) @llvm.structured.gep.p12.s_Ss(%S poison, ptr addrspace(12) @_ZL1s, i32 1) +// 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) ([4 x <{ i32, target("dx.Padding", 12) }>], ptr addrspace(2), ...) @llvm.structured.gep.p2.a4sl_i32tdx.Padding_12ts([4 x <{ i32, target("dx.Padding", 12) }>] poison, ptr addrspace(2) @_ZL1a, i32 2, i32 0) +// 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) ([4 x <{ i32, target("spirv.Padding", 12) }>], ptr addrspace(12), ...) @llvm.structured.gep.p12.a4sl_i32tspirv.Padding_12ts([4 x <{ i32, target("spirv.Padding", 12) }>] poison, ptr addrspace(12) @_ZL1a, i64 2, i32 0) +// 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]; } diff --git a/clang/test/CodeGenHLSL/sgep/object_method.hlsl b/clang/test/CodeGenHLSL/sgep/object_method.hlsl index 42fb5725f008d..f54f0c064bb31 100644 --- a/clang/test/CodeGenHLSL/sgep/object_method.hlsl +++ b/clang/test/CodeGenHLSL/sgep/object_method.hlsl @@ -20,8 +20,11 @@ void foo() { 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-DXIL: %[[#A:]] = call ptr (%struct.O, ptr, ...) @llvm.structured.gep.p0.s_struct.Os(%struct.O poison, ptr %this1, i32 0) -// CHECK-SPIR: %[[#A:]] = call ptr (%struct.O, ptr, ...) @llvm.structured.gep.p0.s_struct.Os(%struct.O poison, ptr %this1, i64 0) +// CHECK-DXIL: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.O) %this1, i32 0) +// CHECK-SPIR: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.O) %this1, i64 0) // CHECK: %[[#B:]] = load i32, ptr %[[#A]], align 1 +// CHECK: ret i32 %[[#B]] diff --git a/llvm/include/llvm/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h index d2f23dc8c1baf..b9e4c06d896b6 100644 --- a/llvm/include/llvm/IR/IRBuilder.h +++ b/llvm/include/llvm/IR/IRBuilder.h @@ -1928,13 +1928,14 @@ class IRBuilderBase { ArrayRef<llvm::Value *> Indices, const Twine &Name = "") { SmallVector<llvm::Value *, 4> Args; - Args.push_back(llvm::PoisonValue::get(BaseType)); Args.push_back(PtrBase); llvm::append_range(Args, Indices); - return cast<StructuredGEPInst>( - CreateIntrinsic(Intrinsic::structured_gep, - {PtrBase->getType(), BaseType}, Args, {}, Name)); + StructuredGEPInst *Output = cast<StructuredGEPInst>(CreateIntrinsic( + Intrinsic::structured_gep, {PtrBase->getType()}, Args, {}, Name)); + Output->addParamAttr( + 0, Attribute::get(getContext(), Attribute::ElementType, BaseType)); + return Output; } Value *CreateGEP(Type *Ty, Value *Ptr, ArrayRef<Value *> IdxList, diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h index f99c3dac683b4..32b7fab0a5844 100644 --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -1816,14 +1816,16 @@ class StructuredGEPInst : public IntrinsicInst { Value *getPointerOperand() const { return getOperand(1); } - Type *getBaseType() const { return getOperand(0)->getType(); } + Type *getBaseType() const { + return getParamAttr(0, Attribute::ElementType).getValueAsType(); + } - unsigned getIndicesCount() const { return getNumOperands() - 3; } + unsigned getIndicesCount() const { return getNumOperands() - 2; } Type *getResultElementType() const { Type *CurrentType = getBaseType(); for (unsigned I = 0; I < getIndicesCount(); I++) { - Value *V = getOperand(I + 2); + Value *V = getOperand(I + 1); ConstantInt *CI = dyn_cast<ConstantInt>(V); if (!CI) return nullptr; diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td index 13d222edc5774..68eb26130684d 100644 --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -1030,9 +1030,8 @@ def int_callbr_landingpad : Intrinsic<[llvm_any_ty], [LLVMMatchType<0>], [IntrNoMerge]>; def int_structured_gep - : Intrinsic<[llvm_anyptr_ty], - [llvm_any_ty, LLVMMatchType<0>, llvm_vararg_ty], - [IntrNoMem, IntrWillReturn]>; + : DefaultAttrsIntrinsic<[llvm_anyptr_ty], + [LLVMMatchType<0>, llvm_vararg_ty], [IntrNoMem]>; //===------------------- Standard C Library Intrinsics --------------------===// // From 474c4b912a7831fa2b084381bc2653e3e5e2beaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Thu, 15 Jan 2026 16:00:08 +0100 Subject: [PATCH 05/16] pr feedback --- llvm/docs/LangRef.rst | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index 26ff48b794e05..c513afba6d42b 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -15010,16 +15010,13 @@ Overview: The '``llvm.structured.gep``' intrinsic (structured **G**\ et\ **E**\ lement\ **P**\ tr) computes a new pointer address resulting of a logical indexing into the ``<source>`` pointer. The returned -address depends on the indices and may depend on the layout of %basetype at -runtime. +address depends on the indices and may depend on the layout of ``<basetype>`` +at runtime. Arguments: """""""""" -``<ty> basetype``: -The actual value passed is ignored, and should be ``poison``. - -``ptr <source>``: +``ptr elementtype(<basetype>) <source>``: A pointer to a memory location assumed to hold a completely laid out value with the same type as ``basetype``. The physical layout of ``basetype`` is target dependent, and is not always known at compile time. @@ -15029,17 +15026,16 @@ GEP with the same source pointer but a different ``basetype``. The ``source`` argument must be annotated with an :ref:`elementtype <attr_elementtype>` attribute at the call-site. This attribute specifies the -type of the element pointed by the pointer source. This type will be -used along with the provided indices and source operands to compute a new -pointer representing the result of a logical indexing into a basetype +type of the element pointed to by the pointer source. This type will be +used along with the provided indices and source operand to compute a new +pointer representing the result of a logical indexing into the basetype pointed by source. ``[i32/i64] index, ...``: -Indices used to traverse into the basetype and determine the target element -this instruction computes an offset for. Indices can be 32-bit or 64-bit -unsigned integers. Indices being handled one by one, both sizes can be mixed -in the same instruction. The precision used to compute the resulting pointer -is target-dependent. +Indices used to traverse into the basetype and compute a pointer to the target +element. Indices can be 32-bit or 64-bit unsigned integers. Indices being +handled one by one, both sizes can be mixed in the same instruction. The +precision used to compute the resulting pointer is target-dependent. When used to index into a struct, only integer constants are allowed. Semantics: @@ -15067,7 +15063,7 @@ element in the logical layout by overflowing: - If the indexed type is a struct with N fields, the index must be an immediate/constant value in the range ``[0; N[``. - If indexing into an array or vector, the index can be a variable, but - assumed to be inbounds with regards to the current basetype logical layout. + is assumed to be inbounds with regards to the current basetype logical layout. - If the traversed type is an array or vector of N elements with ``N > 0``, the index is assumed to belong to ``[0; N[``. - If the traversed type is an array of size ``0``, the array size is assumed @@ -15100,7 +15096,7 @@ Could be translated to: .. code-block:: llvm %A = type { i32, i32, i32, i32 } - %src = call ptr @llvm.structured.gep(%A poison, ptr %my_struct, i32 1) + %src = call ptr @llvm.structured.gep(ptr elementtype(%A) %my_struct, i32 1) %val = load i32, ptr %src **A more complex case** @@ -15136,7 +15132,7 @@ The store is simple: .. code-block:: llvm - %dst = call ptr @llvm.structured.gep(%S poison, ptr %my_struct, i32 0, i32 2, i32 0, i32 1) + %dst = call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 0, i32 2, i32 0, i32 1) store i32 12, ptr %dst But the load depends on a dynamic index. This means accessing logically the @@ -15144,8 +15140,8 @@ first 4 elements is different than accessing the last: .. code-block:: llvm - %firsts = call ptr @llvm.structured.gep(%S poison, ptr %my_struct, i32 0, i32 %index, i32 0) - %last = call ptr @llvm.structured.gep(%S poison, ptr %my_struct, i32 1) + %firsts = call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 0, i32 %index, i32 0) + %last = call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 1) And because :ref:`i_structured_gep` always assumes indexing inbounds, we cannot reach the last element by doing: @@ -15153,7 +15149,7 @@ cannot reach the last element by doing: .. code-block:: llvm ; BAD - call ptr @llvm.structured.gep(%S poison, ptr %my_struct, i32 0, i32 5, i32 0) + call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 0, i32 5, i32 0) If codegen knew nothing about the physical layout of ``%S``, a condition would be required to select between ``%firsts`` and ``%last`` depending on @@ -15166,7 +15162,7 @@ address every vector in the array, generating the following code: .. code-block:: llvm %T = type [ 5 x { <3 x i32>, i32 } ] - %ptr = call ptr @llvm.structured.gep(%T poison, ptr %my_struct, i32 %index, i32 0, i32 1) + %ptr = call ptr @llvm.structured.gep(ptr elementtype(%T) %my_struct, i32 %index, i32 0, i32 1) store i32 12, ptr %ptr This is, however, dependent on context that codegen has an insight on. This From 9b189afd2c185be6efc4e33ecfa92606349ba3c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Fri, 16 Jan 2026 10:26:28 +0100 Subject: [PATCH 06/16] cleanup StructuredGEPInst --- llvm/include/llvm/IR/IRBuilder.h | 2 +- llvm/include/llvm/IR/IntrinsicInst.h | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/llvm/include/llvm/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h index b9e4c06d896b6..d50eabe175b3d 100644 --- a/llvm/include/llvm/IR/IRBuilder.h +++ b/llvm/include/llvm/IR/IRBuilder.h @@ -1927,7 +1927,7 @@ class IRBuilderBase { StructuredGEPInst *CreateStructuredGEP(Type *BaseType, Value *PtrBase, ArrayRef<llvm::Value *> Indices, const Twine &Name = "") { - SmallVector<llvm::Value *, 4> Args; + SmallVector<llvm::Value *> Args; Args.push_back(PtrBase); llvm::append_range(Args, Indices); diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h index 32b7fab0a5844..628f937e6e89c 100644 --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -1814,8 +1814,6 @@ class StructuredGEPInst : public IntrinsicInst { return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V)); } - Value *getPointerOperand() const { return getOperand(1); } - Type *getBaseType() const { return getParamAttr(0, Attribute::ElementType).getValueAsType(); } @@ -1825,14 +1823,10 @@ class StructuredGEPInst : public IntrinsicInst { Type *getResultElementType() const { Type *CurrentType = getBaseType(); for (unsigned I = 0; I < getIndicesCount(); I++) { - Value *V = getOperand(I + 1); - ConstantInt *CI = dyn_cast<ConstantInt>(V); - if (!CI) - return nullptr; - if (ArrayType *AT = dyn_cast<ArrayType>(CurrentType)) { CurrentType = AT->getElementType(); } else if (StructType *ST = dyn_cast<StructType>(CurrentType)) { + ConstantInt *CI = cast<ConstantInt>(getOperand(I + 1)); CurrentType = ST->getElementType(CI->getZExtValue()); } else { // FIXME(Keenuts): add testing reaching those places once initial From 0c85bc114c0b261ce5cac870307ac25f86e35943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Fri, 16 Jan 2026 10:35:42 +0100 Subject: [PATCH 07/16] rework intrinsic inst --- llvm/include/llvm/IR/IntrinsicInst.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h index 628f937e6e89c..a8748de0cad18 100644 --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -1820,13 +1820,18 @@ class StructuredGEPInst : public IntrinsicInst { unsigned getIndicesCount() const { return getNumOperands() - 2; } + Value* getIndexOperand(size_t Index) const { + assert(Index < getIndicesCount()); + return getOperand(Index + 1); + } + Type *getResultElementType() const { Type *CurrentType = getBaseType(); for (unsigned I = 0; I < getIndicesCount(); I++) { if (ArrayType *AT = dyn_cast<ArrayType>(CurrentType)) { CurrentType = AT->getElementType(); } else if (StructType *ST = dyn_cast<StructType>(CurrentType)) { - ConstantInt *CI = cast<ConstantInt>(getOperand(I + 1)); + ConstantInt *CI = cast<ConstantInt>(getIndexOperand(I)); CurrentType = ST->getElementType(CI->getZExtValue()); } else { // FIXME(Keenuts): add testing reaching those places once initial From dd0b18817ae5cafdd68452e0886baadce3c77fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Fri, 16 Jan 2026 10:51:28 +0100 Subject: [PATCH 08/16] update documentation --- llvm/docs/LangRef.rst | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index c513afba6d42b..ef8b92acf1323 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -15070,6 +15070,10 @@ element in the logical layout by overflowing: to be known at runtime, and the instruction assumes the index is always inbound. +In all cases **except** when the accessed type is a 0-sized array**, indexing +out of bounds yields `poison`. When the index value is unknown, optimizations +can use the type bounds to determine the range of values the index can have. + If the source pointer is poison, the instruction returns poison. The resulting pointer belongs to the same address space as ``source``. @@ -15154,10 +15158,11 @@ cannot reach the last element by doing: If codegen knew nothing about the physical layout of ``%S``, a condition would be required to select between ``%firsts`` and ``%last`` depending on the value of ``%index``. -But in our case, the codegen knows that some logical layouts are equivalent -to others (even if the physical layout is unknown). -In this context, it knows that the following type can be used to logically -address every vector in the array, generating the following code: +But if the codegen knows that some logical layouts are lowered to the same +physical layout, it can interchangeably use them. +In this example, the codegen knows `%S` and `%T` have the same physical +layout (even if layout itself is unknown). Hence, the codegen can access +every element in the array with a single structured.gep: .. code-block:: llvm @@ -15165,8 +15170,8 @@ address every vector in the array, generating the following code: %ptr = call ptr @llvm.structured.gep(ptr elementtype(%T) %my_struct, i32 %index, i32 0, i32 1) store i32 12, ptr %ptr -This is, however, dependent on context that codegen has an insight on. This -logical layout equivalence is not a generic rule. +This is, however, dependent on context that codegen has an insight on. The +fact that `%T` and `%S` are equivalent depends on the target & frontend. .. _int_get_dynamic_area_offset: From 8c2595747b8e1a10b63c7b63df8e4bfbcee32260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Fri, 16 Jan 2026 10:53:14 +0100 Subject: [PATCH 09/16] fix missing closed bracket --- llvm/docs/LangRef.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index ef8b92acf1323..aa6cb9c869145 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -15127,8 +15127,8 @@ The frontend knows this struct has a particular physical layout: .. code-block:: llvm - %S = type { [4 x { <3 x i32>, i32 }, <3 x i32>, float } - ; `-> explicit padding + %S = type { [4 x { <3 x i32>, i32 }], <3 x i32>, float } + ; `-> explicit padding ; -> 4 byte padding between each array element, except ; between the last vector and the float. From f7f661a41ee7848a6f629654f9e004d1815c1f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Fri, 16 Jan 2026 10:53:28 +0100 Subject: [PATCH 10/16] clang-format --- llvm/include/llvm/IR/IntrinsicInst.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h index a8748de0cad18..c5324036e30d2 100644 --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -1820,7 +1820,7 @@ class StructuredGEPInst : public IntrinsicInst { unsigned getIndicesCount() const { return getNumOperands() - 2; } - Value* getIndexOperand(size_t Index) const { + Value *getIndexOperand(size_t Index) const { assert(Index < getIndicesCount()); return getOperand(Index + 1); } From 9885d6427e7cf49e2bc914dab8fea30363b07f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Fri, 16 Jan 2026 10:56:49 +0100 Subject: [PATCH 11/16] remove clang part --- clang/include/clang/Basic/LangOptions.def | 1 - clang/include/clang/Options/Options.td | 7 --- clang/lib/CodeGen/CGExpr.cpp | 31 ----------- clang/lib/CodeGen/CGHLSLRuntime.cpp | 33 +++-------- clang/test/CodeGenHLSL/sgep/array_load.hlsl | 52 ------------------ clang/test/CodeGenHLSL/sgep/array_store.hlsl | 55 ------------------- clang/test/CodeGenHLSL/sgep/load_global.hlsl | 45 --------------- .../test/CodeGenHLSL/sgep/object_method.hlsl | 30 ---------- 8 files changed, 7 insertions(+), 247 deletions(-) delete mode 100644 clang/test/CodeGenHLSL/sgep/array_load.hlsl delete mode 100644 clang/test/CodeGenHLSL/sgep/array_store.hlsl delete mode 100644 clang/test/CodeGenHLSL/sgep/load_global.hlsl delete mode 100644 clang/test/CodeGenHLSL/sgep/object_method.hlsl diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index dcae56e4261ec..8cba1dbaee24e 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -248,7 +248,6 @@ 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 instructions instead of GEP") 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 b98dbaef7c5f6..d4dd71b9d1bea 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -9792,13 +9792,6 @@ 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 541ca00191d93..896c60b13c160 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -4468,17 +4468,6 @@ Address CodeGenFunction::EmitArrayToPointerDecay(const Expr *E, if (!E->getType()->isVariableArrayType()) { assert(isa<llvm::ArrayType>(Addr.getElementType()) && "Expected pointer to array"); - - if (getLangOpts().EmitStructuredGEP) { - llvm::Value *Ptr = Addr.emitRawPointer(*this); - if (auto *C = dyn_cast<llvm::Constant>(Ptr)) - return Address(C, Addr.getElementType(), Addr.getAlignment(), - Addr.isKnownNonNull()); - return Address( - Builder.CreateStructuredGEP(NewTy, Addr.getBasePointer(), {}), - Addr.getElementType(), Addr.getAlignment(), Addr.isKnownNonNull()); - } - Addr = Builder.CreateConstArrayGEP(Addr, 0, "arraydecay"); } @@ -4518,9 +4507,6 @@ static llvm::Value *emitArraySubscriptGEP(CodeGenFunction &CGF, bool signedIndices, SourceLocation loc, const llvm::Twine &name = "arrayidx") { - if (CGF.getLangOpts().EmitStructuredGEP) - return CGF.Builder.CreateStructuredGEP(elemType, ptr, indices); - if (inbounds) { return CGF.EmitCheckedInBoundsGEP(elemType, ptr, indices, signedIndices, CodeGenFunction::NotSubtraction, loc, @@ -4532,17 +4518,10 @@ 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 (arrayType && 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, @@ -4659,8 +4638,6 @@ 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; @@ -5515,14 +5492,6 @@ 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 e2230952cc85e..e796bbb5855c8 100644 --- a/clang/lib/CodeGen/CGHLSLRuntime.cpp +++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp @@ -1113,12 +1113,7 @@ static void initializeBuffer(CodeGenModule &CGM, llvm::GlobalVariable *GV, /*ReturnType=*/HandleTy, IntrID, Args, nullptr, Twine(GV->getName()).concat("_h")); - llvm::Value *HandleRef = - CGM.getLangOpts().EmitStructuredGEP - ? Builder.CreateStructuredGEP(GV->getValueType(), GV, - llvm::ConstantInt::get(CGM.IntTy, 0)) - : Builder.CreateStructGEP(GV->getValueType(), GV, 0); - + llvm::Value *HandleRef = Builder.CreateStructGEP(GV->getValueType(), GV, 0); Builder.CreateAlignedStore(CreateHandle, HandleRef, HandleRef->getPointerAlignment(DL)); Builder.CreateRetVoid(); @@ -1402,18 +1397,9 @@ std::optional<LValue> CGHLSLRuntime::emitBufferArraySubscriptExpr( Indices.push_back(Idx); Indices.push_back(llvm::ConstantInt::get(CGF.Int32Ty, 0)); - if (CGF.getLangOpts().EmitStructuredGEP) { - if (auto *AT = dyn_cast<llvm::ArrayType>(Addr.getElementType())) - LayoutTy = llvm::ArrayType::get(LayoutTy, AT->getNumElements()); - auto *GEP = CGF.Builder.CreateStructuredGEP( - LayoutTy, Addr.emitRawPointer(CGF), Indices, "cbufferidx"); - Addr = - Address(GEP, GEP->getResultElementType(), RowAlignedSize, KnownNonNull); - } else { - llvm::Value *GEP = CGF.Builder.CreateGEP(LayoutTy, Addr.emitRawPointer(CGF), - Indices, "cbufferidx"); - Addr = Address(GEP, Addr.getElementType(), RowAlignedSize, KnownNonNull); - } + 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); } @@ -1593,14 +1579,9 @@ LValue CGHLSLRuntime::emitBufferMemberExpr(CodeGenFunction &CGF, llvm::Type *FieldLLVMTy = CGM.getTypes().ConvertTypeForMem(FieldType); CharUnits Align = CharUnits::fromQuantity( CGF.CGM.getDataLayout().getABITypeAlign(FieldLLVMTy)); - - 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); + Address Addr(CGF.Builder.CreateStructGEP(LayoutTy, Base.getPointer(CGF), + FieldIdx, Field->getName()), + 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 deleted file mode 100644 index 26efa7cef57dc..0000000000000 --- a/clang/test/CodeGenHLSL/sgep/array_load.hlsl +++ /dev/null @@ -1,52 +0,0 @@ -// 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 - -void foo() { -// CHECK: %array = alloca [3 x i32], align 4 - uint array[3] = { 0, 1, 2 }; - -// CHECK-DXIL: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x i32]) %array, i32 2) -// CHECK-SPIR: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x i32]) %array, 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-DXIL: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %array, i32 2) -// CHECK-DXIL: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#A]], i32 1) -// CHECK-SPIR: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %array, i64 2) -// CHECK-SPIR: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#A]], 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-DXIL: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([2 x %struct.S2]) %array, i32 1) -// CHECK-DXIL: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %[[#A]], i32 1) -// CHECK-DXIL: %[[#C:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#B]], i32 0) - -// CHECK-SPIR: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([2 x %struct.S2]) %array, i64 1) -// CHECK-SPIR: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %[[#A]], i64 1) -// CHECK-SPIR: %[[#C:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#B]], 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 deleted file mode 100644 index a798bc47da50c..0000000000000 --- a/clang/test/CodeGenHLSL/sgep/array_store.hlsl +++ /dev/null @@ -1,55 +0,0 @@ -// 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 - -[shader("compute")] -[numthreads(1,1,1)] -void foo() { -// CHECK: %array = alloca [10 x i32], align 4 - uint array[10]; - -// CHECK-DXIL: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %array, i32 2) -// CHECK-SPIR: %[[#PTR:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([10 x i32]) %array, 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-DXIL: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %array, i32 2) -// CHECK-DXIL: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#A]], i32 1) - -// CHECK-SPIR: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([3 x %struct.S]) %array, i64 2) -// CHECK-SPIR: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#A]], 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-DXIL: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([2 x %struct.S2]) %array, i32 1) -// CHECK-DXIL: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %[[#A]], i32 1) -// CHECK-DXIL: %[[#C:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#B]], i32 0) - -// CHECK-SPIR: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([2 x %struct.S2]) %array, i64 1) -// CHECK-SPIR: %[[#B:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S2) %[[#A]], i64 1) -// CHECK-SPIR: %[[#C:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.S) %[[#B]], 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 deleted file mode 100644 index 18e57e04e64fc..0000000000000 --- a/clang/test/CodeGenHLSL/sgep/load_global.hlsl +++ /dev/null @@ -1,45 +0,0 @@ -// 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 deleted file mode 100644 index f54f0c064bb31..0000000000000 --- a/clang/test/CodeGenHLSL/sgep/object_method.hlsl +++ /dev/null @@ -1,30 +0,0 @@ -// 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-DXIL: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.O) %this1, i32 0) -// CHECK-SPIR: %[[#A:]] = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%struct.O) %this1, i64 0) -// CHECK: %[[#B:]] = load i32, ptr %[[#A]], align 1 -// CHECK: ret i32 %[[#B]] From f51465302d6526f08c5b61b642177f005ac40887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Fri, 16 Jan 2026 11:24:55 +0100 Subject: [PATCH 12/16] rephrase assumption about source --- llvm/docs/LangRef.rst | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index aa6cb9c869145..7cc112d62ef0c 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -15017,12 +15017,7 @@ Arguments: """""""""" ``ptr elementtype(<basetype>) <source>``: -A pointer to a memory location assumed to hold a completely laid out value -with the same type as ``basetype``. The physical layout of ``basetype`` is -target dependent, and is not always known at compile time. -The assumption this instruction makes on the memory location is only relevant -to this particular call. A frontend could possibly emit multiple structured -GEP with the same source pointer but a different ``basetype``. +A pointer to the memory location used as base for the address computation. The ``source`` argument must be annotated with an :ref:`elementtype <attr_elementtype>` attribute at the call-site. This attribute specifies the @@ -15031,9 +15026,13 @@ used along with the provided indices and source operand to compute a new pointer representing the result of a logical indexing into the basetype pointed by source. +The ``basetype`` is only associated with ``<source>`` for this particular +call. A frontend could possibly emit multiple structured +GEP with the same source pointer but a different ``basetype``. + ``[i32/i64] index, ...``: -Indices used to traverse into the basetype and compute a pointer to the target -element. Indices can be 32-bit or 64-bit unsigned integers. Indices being +Indices used to traverse into the ``basetype`` and compute a pointer to the +target element. Indices can be 32-bit or 64-bit unsigned integers. Indices being handled one by one, both sizes can be mixed in the same instruction. The precision used to compute the resulting pointer is target-dependent. When used to index into a struct, only integer constants are allowed. From 8d590a095cc406f507406a338a46c4d764a30586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Fri, 16 Jan 2026 13:24:37 +0100 Subject: [PATCH 13/16] remove mention of UB and valid memory requirements --- llvm/docs/LangRef.rst | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index 7cc112d62ef0c..d813c31797d6c 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -15072,17 +15072,9 @@ element in the logical layout by overflowing: In all cases **except** when the accessed type is a 0-sized array**, indexing out of bounds yields `poison`. When the index value is unknown, optimizations can use the type bounds to determine the range of values the index can have. - If the source pointer is poison, the instruction returns poison. The resulting pointer belongs to the same address space as ``source``. - -This instruction assumes the pointer ``source`` points to a valid memory -location large enough to contain the physically laid out version ``basetype``. -This instruction does not dereference any pointer, but requires the source -operand to be a valid memory location. Meaning this instruction cannot be -used as an ``offsetof`` by providing ``ptr 0`` as source. -If the memory location pointed by source is not large enough, using the -resulting pointer to access memory yields an undefined behavior. +This instruction does not dereference the pointer. Example: """""""" From b5226c1d90eb5d7f89dddd20fa3c7124d9d398bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Tue, 20 Jan 2026 10:17:09 +0100 Subject: [PATCH 14/16] remove frontend origin part of sentence --- llvm/docs/LangRef.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index d813c31797d6c..8f92f773f2f80 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -15162,7 +15162,7 @@ every element in the array with a single structured.gep: store i32 12, ptr %ptr This is, however, dependent on context that codegen has an insight on. The -fact that `%T` and `%S` are equivalent depends on the target & frontend. +fact that `%T` and `%S` are equivalent depends on the target. .. _int_get_dynamic_area_offset: From 0f0ca2ccadfbd1f613931466bdbcf4ae683b2044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Tue, 20 Jan 2026 12:57:16 +0100 Subject: [PATCH 15/16] pr-feedback --- llvm/docs/LangRef.rst | 76 +++++++++------------------- llvm/include/llvm/IR/IRBuilder.h | 2 +- llvm/include/llvm/IR/IntrinsicInst.h | 8 +-- llvm/include/llvm/IR/Intrinsics.td | 3 +- 4 files changed, 30 insertions(+), 59 deletions(-) diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index 8f92f773f2f80..622d4dacebb6b 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -15008,9 +15008,10 @@ Syntax: Overview: """"""""" -The '``llvm.structured.gep``' intrinsic (structured **G**\ et\ **E**\ lement\ **P**\ tr) computes a new pointer address -resulting of a logical indexing into the ``<source>`` pointer. The returned -address depends on the indices and may depend on the layout of ``<basetype>`` +The '``llvm.structured.gep``' intrinsic (structured +**G**\ et\ **E**\ lement\ **P**\ tr) computes a new pointer address resulting +from a logical indexing into the ``<source>`` pointer. The returned address +depends on the indices and may depend on the layout of ``<basetype>`` at runtime. Arguments: @@ -15067,9 +15068,9 @@ element in the logical layout by overflowing: the index is assumed to belong to ``[0; N[``. - If the traversed type is an array of size ``0``, the array size is assumed to be known at runtime, and the instruction assumes the index is always - inbound. + inbounds. -In all cases **except** when the accessed type is a 0-sized array**, indexing +In all cases **except** when the accessed type is a 0-sized array, indexing out of bounds yields `poison`. When the index value is unknown, optimizations can use the type bounds to determine the range of values the index can have. If the source pointer is poison, the instruction returns poison. @@ -15103,66 +15104,35 @@ Let’s consider the following code: .. code-block:: cpp struct S { - uint3 array[5]; - float my_float; + uint a; + uint b; + uint c; + uint d; } - my_struct->array[2].y = 12; - int val = my_struct->array[index].y; + int val = my_struct->b; -The frontend knows this struct has a particular physical layout: - - an array of 5 vectors of 3 integers, aligned on 16 bytes. - - one float at the end, with no alignment requirement, so packed right - after the last ``uint3``, which is a ``<3 x uint>``. +The frontend in this example knows the following types have the same physical +layout (even if it doesn't know the exact physical layout): + - `{ i32, i32, i32, i32 }` + - `[ i32 x 4 ]` -.. code-block:: llvm - - %S = type { [4 x { <3 x i32>, i32 }], <3 x i32>, float } - ; `-> explicit padding - ; -> 4 byte padding between each array element, except - ; between the last vector and the float. - -The store is simple: +This means is is valid to lower the following code to either: .. code-block:: llvm + %S = type { i32, i32, i32, i32 } + %src = call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 1) + load i32, ptr %src - %dst = call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 0, i32 2, i32 0, i32 1) - store i32 12, ptr %dst - -But the load depends on a dynamic index. This means accessing logically the -first 4 elements is different than accessing the last: +Or: .. code-block:: llvm - - %firsts = call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 0, i32 %index, i32 0) - %last = call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 1) - -And because :ref:`i_structured_gep` always assumes indexing inbounds, we -cannot reach the last element by doing: - -.. code-block:: llvm - - ; BAD - call ptr @llvm.structured.gep(ptr elementtype(%S) %my_struct, i32 0, i32 5, i32 0) - -If codegen knew nothing about the physical layout of ``%S``, a condition -would be required to select between ``%firsts`` and ``%last`` depending on -the value of ``%index``. -But if the codegen knows that some logical layouts are lowered to the same -physical layout, it can interchangeably use them. -In this example, the codegen knows `%S` and `%T` have the same physical -layout (even if layout itself is unknown). Hence, the codegen can access -every element in the array with a single structured.gep: - -.. code-block:: llvm - - %T = type [ 5 x { <3 x i32>, i32 } ] - %ptr = call ptr @llvm.structured.gep(ptr elementtype(%T) %my_struct, i32 %index, i32 0, i32 1) - store i32 12, ptr %ptr + %src = call ptr @llvm.structured.gep(ptr elementtype([ 4 x i32 ]) %my_struct, i32 1) + load i32, ptr %src This is, however, dependent on context that codegen has an insight on. The -fact that `%T` and `%S` are equivalent depends on the target. +fact that `[ i32 x 4 ]` and `%S` are equivalent depends on the target. .. _int_get_dynamic_area_offset: diff --git a/llvm/include/llvm/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h index d50eabe175b3d..daa50637c8665 100644 --- a/llvm/include/llvm/IR/IRBuilder.h +++ b/llvm/include/llvm/IR/IRBuilder.h @@ -1925,7 +1925,7 @@ class IRBuilderBase { } StructuredGEPInst *CreateStructuredGEP(Type *BaseType, Value *PtrBase, - ArrayRef<llvm::Value *> Indices, + ArrayRef<Value *> Indices, const Twine &Name = "") { SmallVector<llvm::Value *> Args; Args.push_back(PtrBase); diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h index c5324036e30d2..307e5f283e30d 100644 --- a/llvm/include/llvm/IR/IntrinsicInst.h +++ b/llvm/include/llvm/IR/IntrinsicInst.h @@ -1818,16 +1818,16 @@ class StructuredGEPInst : public IntrinsicInst { return getParamAttr(0, Attribute::ElementType).getValueAsType(); } - unsigned getIndicesCount() const { return getNumOperands() - 2; } + unsigned getNumIndices() const { return getNumOperands() - 2; } Value *getIndexOperand(size_t Index) const { - assert(Index < getIndicesCount()); + assert(Index < getNumIndices()); return getOperand(Index + 1); } Type *getResultElementType() const { Type *CurrentType = getBaseType(); - for (unsigned I = 0; I < getIndicesCount(); I++) { + for (unsigned I = 0; I < getNumIndices(); I++) { if (ArrayType *AT = dyn_cast<ArrayType>(CurrentType)) { CurrentType = AT->getElementType(); } else if (StructType *ST = dyn_cast<StructType>(CurrentType)) { @@ -1836,7 +1836,7 @@ class StructuredGEPInst : public IntrinsicInst { } else { // FIXME(Keenuts): add testing reaching those places once initial // implementation has landed. - assert(0); + llvm_unreachable("unimplemented"); } } diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td index 68eb26130684d..5c07108acae32 100644 --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -1031,7 +1031,8 @@ def int_callbr_landingpad : Intrinsic<[llvm_any_ty], [LLVMMatchType<0>], def int_structured_gep : DefaultAttrsIntrinsic<[llvm_anyptr_ty], - [LLVMMatchType<0>, llvm_vararg_ty], [IntrNoMem]>; + [LLVMMatchType<0>, llvm_vararg_ty], + [IntrNoMem, IntrSpeculatable]>; //===------------------- Standard C Library Intrinsics --------------------===// // From d664c17f6f8c9cc8dfff255a611de884cf2f6284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <[email protected]> Date: Tue, 20 Jan 2026 13:50:36 +0100 Subject: [PATCH 16/16] add verifier checks --- llvm/lib/IR/Verifier.cpp | 35 +++++++++++ .../test/Verifier/structured-gep-attribute.ll | 15 +++++ .../Verifier/structured-gep-indices-bad.ll | 43 ++++++++++++++ llvm/test/Verifier/structured-gep-indices.ll | 59 +++++++++++++++++++ 4 files changed, 152 insertions(+) create mode 100644 llvm/test/Verifier/structured-gep-attribute.ll create mode 100644 llvm/test/Verifier/structured-gep-indices-bad.ll create mode 100644 llvm/test/Verifier/structured-gep-indices.ll diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp index 38da185226a92..8e6491f63263d 100644 --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -6878,6 +6878,41 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) { &Call); break; } + case Intrinsic::structured_gep: { + // Parser should refuse those 2 cases. + assert(Call.getNumOperands() >= 2); + assert(Call.getOperand(0)->getType()->isPointerTy()) + + Check(Call.paramHasAttr(0, Attribute::ElementType), + "Intrinsic first parameter is missing an ElementType attribute", + &Call); + + Type *T = Call.getParamAttr(0, Attribute::ElementType).getValueAsType(); + for (unsigned I = 1; I < Call.getNumOperands() - 1; ++I) { + Value *Index = Call.getOperand(I); + ConstantInt *CI = dyn_cast<ConstantInt>(Index); + Check(Index->getType()->isIntegerTy(), + "Index operand type must be an integer", &Call); + + if (ArrayType *AT = dyn_cast<ArrayType>(T)) { + T = AT->getElementType(); + if (CI && AT->getNumElements() != 0) + Check(CI->getZExtValue() < AT->getNumElements(), + "Indexing in an array should be inbounds", &Call); + } else if (StructType *ST = dyn_cast<StructType>(T)) { + Check(CI, "Indexing into a struct requires a constant int", &Call); + Check(CI->getZExtValue() < ST->getNumElements(), + "Indexing in a struct should be inbounds", &Call); + T = ST->getElementType(CI->getZExtValue()); + } else if (VectorType *VT = dyn_cast<VectorType>(T)) { + T = VT->getElementType(); + } else { + CheckFailed("Reached a non-composite type with more indices to process", + &Call); + } + } + break; + } case Intrinsic::amdgcn_cs_chain: { auto CallerCC = Call.getCaller()->getCallingConv(); switch (CallerCC) { diff --git a/llvm/test/Verifier/structured-gep-attribute.ll b/llvm/test/Verifier/structured-gep-attribute.ll new file mode 100644 index 0000000000000..66f61b9d82607 --- /dev/null +++ b/llvm/test/Verifier/structured-gep-attribute.ll @@ -0,0 +1,15 @@ +; RUN: not llvm-as -disable-output %s 2>&1 | FileCheck %s + +target datalayout = "e-p:32:32:32-p1:16:16:16-p2:8:8:8-i1:8:32-i8:8:32-i16:16:32-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:32:64-v128:32:128-a0:0:32-n8:16:32" + +%S = type { i32, i32 } + +@global = global %S zeroinitializer + +; CHECK: Intrinsic first parameter is missing an ElementType attribute + +define void @foo() { +entry: + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr @global, i32 0) + ret void +} diff --git a/llvm/test/Verifier/structured-gep-indices-bad.ll b/llvm/test/Verifier/structured-gep-indices-bad.ll new file mode 100644 index 0000000000000..23428b9bf69e3 --- /dev/null +++ b/llvm/test/Verifier/structured-gep-indices-bad.ll @@ -0,0 +1,43 @@ +; RUN: not llvm-as -disable-output %s 2>&1 | FileCheck %s + +target datalayout = "e-p:32:32:32-p1:16:16:16-p2:8:8:8-i1:8:32-i8:8:32-i16:16:32-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:32:64-v128:32:128-a0:0:32-n8:16:32" + +%S = type { i32, i32 } + +@global = global %S zeroinitializer + + +define void @foo(ptr %src) { +entry: +; CHECK: Reached a non-composite type with more indices to process + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%S) %src, i32 0, i32 0) + ret void +} + +define void @bar(ptr %src) { +entry: +; CHECK: Indexing in a struct should be inbounds + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%S) %src, i32 2) + ret void +} + +define void @baz(ptr %src, i32 %index) { +entry: +; CHECK: Indexing into a struct requires a constant int + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%S) %src, i32 %index) + ret void +} + +define void @biz(ptr %src, i32 %index) { +entry: +; CHECK: Indexing in an array should be inbounds + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([ 2 x i32 ]) %src, i32 2) + ret void +} + +define void @fiz(ptr %src) { +entry: +; CHECK: Index operand type must be an integer + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([ 2 x i32 ]) %src, float 1.0) + ret void +} diff --git a/llvm/test/Verifier/structured-gep-indices.ll b/llvm/test/Verifier/structured-gep-indices.ll new file mode 100644 index 0000000000000..1105f0ed0739c --- /dev/null +++ b/llvm/test/Verifier/structured-gep-indices.ll @@ -0,0 +1,59 @@ +; RUN: llvm-as -disable-output %s + +target datalayout = "e-p:32:32:32-p1:16:16:16-p2:8:8:8-i1:8:32-i8:8:32-i16:16:32-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:32:64-v128:32:128-a0:0:32-n8:16:32" + +%S = type { i32, i32 } + +define void @foo(ptr %src, i32 %index) { +entry: + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([0 x %S]) %src, i32 %index, i32 1) + ret void +} + +define void @bar(ptr %src, i32 %index) { +entry: + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([2 x %S]) %src, i32 %index, i32 1) + ret void +} + +define void @baz(ptr %src) { +entry: + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([2 x %S]) %src, i32 1, i32 1) + ret void +} + +define void @biz(ptr %src) { +entry: + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(%S) %src, i32 0) + ret void +} + +define void @buz(ptr %src) { +entry: + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([ 3 x [ 2 x i32 ] ]) %src, i32 2, i32 1) + ret void +} + +define void @boz(ptr %src) { +entry: + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([ 0 x i32 ]) %src, i32 1) + ret void +} + +define void @foz(ptr %src) { +entry: + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype(i32) %src) + ret void +} + +define void @fiz(ptr %src) { +entry: + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([ 0 x i32 ]) %src, i64 1) + ret void +} + +define void @fuz(ptr %src) { +entry: + %ptr = call ptr (ptr, ...) @llvm.structured.gep.p0(ptr elementtype([ 0 x i32 ]) %src, i8 1) + ret void +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
