[llvm] [IR] Add @llvm.structured.alloca (PR #186811)
Nathan Gauër via llvm-commits
llvm-commits at lists.llvm.org
Mon Mar 30 07:42:15 PDT 2026
https://github.com/Keenuts updated https://github.com/llvm/llvm-project/pull/186811
>From 9fafd990a89a5cffe6ca69587cb14caf895cc8ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Mon, 16 Mar 2026 15:11:56 +0100
Subject: [PATCH 1/6] [IR] Add @llvm.structured.alloca
This instruction is an alternative for the `alloca` instruction
when targeting logical targets like DXIL/SPIR-V.
This instruction allocates some memory, but the exact size of the
allocation is not known at the IR level. Only some equivalence can
be determined.
Commit adds docs, instruction declaration, and IR verifier testing.
Related to: https://discourse.llvm.org/t/rfc-adding-logical-structured-alloca/
---
llvm/docs/LangRef.rst | 89 ++++++++++++++++++++-
llvm/include/llvm/IR/Attributes.td | 2 +-
llvm/include/llvm/IR/IntrinsicInst.h | 15 ++++
llvm/include/llvm/IR/Intrinsics.td | 2 +
llvm/lib/IR/Verifier.cpp | 8 +-
llvm/test/Verifier/structured-alloca-bad.ll | 8 ++
llvm/test/Verifier/structured-alloca.ll | 15 ++++
7 files changed, 133 insertions(+), 6 deletions(-)
create mode 100644 llvm/test/Verifier/structured-alloca-bad.ll
create mode 100644 llvm/test/Verifier/structured-alloca.ll
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 8764ed51f8480..5ca585199cb48 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1457,10 +1457,11 @@ Currently, only the following parameter attributes are defined:
present and assign it particular semantics. This will be documented on
individual intrinsics.
- The attribute may only be applied to pointer typed arguments of intrinsic
- calls. It cannot be applied to non-intrinsic calls, and cannot be applied
- to parameters on function declarations. For non-opaque pointers, the type
- passed to ``elementtype`` must match the pointer element type.
+ The attribute may only be applied to pointer typed arguments or return
+ values of intrinsic calls. It cannot be applied to non-intrinsic calls,
+ and cannot be applied to parameters on function declarations.
+ For non-opaque pointers, the type passed to ``elementtype`` must match
+ the pointer element type.
.. _attr_align:
@@ -15217,6 +15218,86 @@ This is, however, dependent on context that codegen has an insight on. The
fact that `[ i32 x 4 ]` and `%S` are equivalent depends on the target.
+.. _i_structured_alloca:
+
+'``llvm.structured.alloca``' Intrinsic
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Syntax:
+"""""""
+
+::
+
+ declare ptr elementtype(<allocated_type>)
+ @llvm.structured.alloca()
+
+Overview:
+"""""""""
+
+The '``llvm.structured.alloca``' intrinsic allocates uninitialized memory on
+the stack for a logical type, such as an aggregate, an array, or a scalar.
+
+Unlike the standard :ref:`alloca <i_alloca>` instruction, the physical memory
+layout of a ``llvm.structured.alloca`` is completely opaque to the IR.
+Exact padding, size, and subtype offsets is target-depentent and may differ
+from the standard ``DataLayout``.
+
+Arguments:
+""""""""""
+
+The intrinsic must be annotated with an :ref:`elementtype <attr_elementtype>`
+attribute at the call-site on the return value. This attribute specifies the
+type of the allocated element.
+
+Semantics:
+""""""""""
+
+The ``llvm.structured.alloca`` intrinsic allocates uninitialized memory for a
+logical type. Loading from uninitialized memory produces an undefined value.
+This intrinsic does not guarantee that the allocated memory will store a value
+of the given type, only that it allocates enough space for it on the destination
+target. While the type's size and layout are constant (independent of location),
+the exact padding and offsets between subtypes are opaque to the IR and are
+determined by the target's backend.
+
+The resulting pointer is in the :ref:`alloca address space <alloca_addrspace>`
+defined in the :ref:`datalayout string <langref_datalayout>`.
+
+Standard pointer arithmetic (``getelementptr``, ``ptradd``) and lifetime
+intrinsics (:ref:`llvm.lifetime.start <int_lifestart>`,
+:ref:`llvm.lifetime.end <int_lifeend>`) are permitted on the returned pointer.
+However, because the physical layout is opaque, using physical pointer
+arithmetic requires the frontend or emitting pass to have explicit knowledge of
+the backend's layout rules.
+
+Example:
+""""""""
+
+.. code-block:: llvm
+
+ %S = type { i32, i32, i32, i32 }
+
+ ; Allocate one instance of %S on the stack
+ %ptr = call ptr elementtype(%S) @llvm.structured.alloca()
+
+ ; Access the second field of the allocated struct
+ %field_ptr = call ptr @llvm.structured.gep(ptr elementtype(%S) %ptr, i32 1)
+ %val = load i32, ptr %field_ptr
+
+ ; Allocate an array of 10 i32s on the stack
+ %array_ptr = call ptr elementtype([10 x i32]) @llvm.structured.alloca()
+
+ ; Allocate a single i32 on the stack
+ %scalar_ptr = call ptr elementtype(i32) @llvm.structured.alloca()
+
+ ; Although the exact size of 'i32' or '%S' is opaque, it is constant
+ ; for the duration of the module. This allows, for example, reusing
+ ; an allocation slot for two different values of the same type.
+ %a = call ptr elementtype(float) @llvm.structured.alloca()
+ %b = call ptr elementtype(float) @llvm.structured.alloca()
+ ; %a and %b are guaranteed to have the same allocation size.
+
+
.. _int_get_dynamic_area_offset:
'``llvm.get.dynamic.area.offset``' Intrinsic
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index 941251003f5ba..d03207380cf7c 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -132,7 +132,7 @@ def DereferenceableOrNull : IntAttr<"dereferenceable_or_null", IntersectMin,
def DisableSanitizerInstrumentation: EnumAttr<"disable_sanitizer_instrumentation", IntersectPreserve, [FnAttr]>;
/// Provide pointer element type to intrinsic.
-def ElementType : TypeAttr<"elementtype", IntersectPreserve, [ParamAttr]>;
+def ElementType : TypeAttr<"elementtype", IntersectPreserve, [RetAttr, ParamAttr]>;
/// Whether to keep return instructions, or replace with a jump to an external
/// symbol.
diff --git a/llvm/include/llvm/IR/IntrinsicInst.h b/llvm/include/llvm/IR/IntrinsicInst.h
index 784e599c53f44..78a0cd569d5bd 100644
--- a/llvm/include/llvm/IR/IntrinsicInst.h
+++ b/llvm/include/llvm/IR/IntrinsicInst.h
@@ -1804,6 +1804,21 @@ class ConvergenceControlInst : public IntrinsicInst {
CreateLoop(BasicBlock &BB, ConvergenceControlInst *Parent);
};
+class StructuredAllocaInst : public IntrinsicInst {
+public:
+ static bool classof(const IntrinsicInst *I) {
+ return I->getIntrinsicID() == Intrinsic::structured_alloca;
+ }
+
+ static bool classof(const Value *V) {
+ return isa<IntrinsicInst>(V) && classof(cast<IntrinsicInst>(V));
+ }
+
+ Type *getAllocationType() const {
+ return getRetAttr(Attribute::ElementType).getValueAsType();
+ }
+};
+
class StructuredGEPInst : public IntrinsicInst {
public:
static bool classof(const IntrinsicInst *I) {
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 5b5fffaa48951..e90462c15e6d5 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -1034,6 +1034,8 @@ def int_structured_gep
[LLVMMatchType<0>, llvm_vararg_ty],
[IntrNoMem, IntrSpeculatable]>;
+def int_structured_alloca : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [], []>;
+
//===------------------- Standard C Library Intrinsics --------------------===//
//
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index f986f5406b2b3..be1aecce173fe 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -6929,6 +6929,11 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
}
break;
}
+ case Intrinsic::structured_alloca:
+ Check(Call.hasRetAttr(Attribute::ElementType),
+ "@llvm.structured.alloca calls require elementtype attribute.",
+ &Call);
+ break;
case Intrinsic::amdgcn_cs_chain: {
auto CallerCC = Call.getCaller()->getCallingConv();
switch (CallerCC) {
@@ -7206,7 +7211,8 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end: {
Value *Ptr = Call.getArgOperand(0);
- Check(isa<AllocaInst>(Ptr) || isa<PoisonValue>(Ptr),
+ Check(isa<AllocaInst>(Ptr) || isa<PoisonValue>(Ptr) ||
+ isa<IntrinsicInst>(Ptr),
"llvm.lifetime.start/end can only be used on alloca or poison",
&Call);
break;
diff --git a/llvm/test/Verifier/structured-alloca-bad.ll b/llvm/test/Verifier/structured-alloca-bad.ll
new file mode 100644
index 0000000000000..e3c9ac8e443c7
--- /dev/null
+++ b/llvm/test/Verifier/structured-alloca-bad.ll
@@ -0,0 +1,8 @@
+; RUN: not llvm-as -disable-output %s 2>&1 | FileCheck %s
+
+define void @missing_attribute() {
+entry:
+; CHECK: @llvm.structured.alloca calls require elementtype attribute.
+ %ptr = call ptr @llvm.structured.alloca()
+ ret void
+}
diff --git a/llvm/test/Verifier/structured-alloca.ll b/llvm/test/Verifier/structured-alloca.ll
new file mode 100644
index 0000000000000..5400de49c64d9
--- /dev/null
+++ b/llvm/test/Verifier/structured-alloca.ll
@@ -0,0 +1,15 @@
+; RUN: llvm-as -disable-output %s
+
+%S = type { i32, i32 }
+
+define void @simple_scalar_allocation() {
+entry:
+ %ptr = call elementtype(i32) ptr @llvm.structured.alloca()
+ ret void
+}
+
+define void @struct_allocation() {
+entry:
+ %ptr = call elementtype(%S) ptr @llvm.structured.alloca()
+ ret void
+}
>From 9fce0a01bc19ea2233eb6443bcf946ed6ffca433 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Tue, 24 Mar 2026 16:40:13 +0100
Subject: [PATCH 2/6] pr-feedback: flip operand order
---
llvm/docs/LangRef.rst | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 5ca585199cb48..4a92a9c92556f 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -15228,7 +15228,7 @@ Syntax:
::
- declare ptr elementtype(<allocated_type>)
+ declare elementtype(<allocated_type>) ptr
@llvm.structured.alloca()
Overview:
@@ -15278,23 +15278,23 @@ Example:
%S = type { i32, i32, i32, i32 }
; Allocate one instance of %S on the stack
- %ptr = call ptr elementtype(%S) @llvm.structured.alloca()
+ %ptr = call elementtype(%S) ptr @llvm.structured.alloca()
; Access the second field of the allocated struct
%field_ptr = call ptr @llvm.structured.gep(ptr elementtype(%S) %ptr, i32 1)
%val = load i32, ptr %field_ptr
; Allocate an array of 10 i32s on the stack
- %array_ptr = call ptr elementtype([10 x i32]) @llvm.structured.alloca()
+ %array_ptr = call elementtype([10 x i32]) ptr @llvm.structured.alloca()
; Allocate a single i32 on the stack
- %scalar_ptr = call ptr elementtype(i32) @llvm.structured.alloca()
+ %scalar_ptr = call elementtype(i32) ptr @llvm.structured.alloca()
; Although the exact size of 'i32' or '%S' is opaque, it is constant
; for the duration of the module. This allows, for example, reusing
; an allocation slot for two different values of the same type.
- %a = call ptr elementtype(float) @llvm.structured.alloca()
- %b = call ptr elementtype(float) @llvm.structured.alloca()
+ %a = call elementtype(float) ptr @llvm.structured.alloca()
+ %b = call elementtype(float) ptr @llvm.structured.alloca()
; %a and %b are guaranteed to have the same allocation size.
>From 2667c70c6277c9ffd6d863b99702d61cea4906b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Thu, 26 Mar 2026 14:06:35 +0100
Subject: [PATCH 3/6] pr-feedback
---
llvm/docs/LangRef.rst | 16 +++++++++-------
llvm/include/llvm/IR/Intrinsics.td | 2 +-
llvm/lib/IR/Verifier.cpp | 3 ++-
3 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 4a92a9c92556f..caefa4495e460 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -15239,7 +15239,7 @@ the stack for a logical type, such as an aggregate, an array, or a scalar.
Unlike the standard :ref:`alloca <i_alloca>` instruction, the physical memory
layout of a ``llvm.structured.alloca`` is completely opaque to the IR.
-Exact padding, size, and subtype offsets is target-depentent and may differ
+Exact padding, size, and subtype offsets is target-dependent and may differ
from the standard ``DataLayout``.
Arguments:
@@ -27814,8 +27814,8 @@ object's lifetime.
Arguments:
""""""""""
-The argument is either a pointer to an ``alloca`` instruction or a ``poison``
-value.
+The argument is either a pointer to an ``alloca`` instruction, a ``poison``
+value, or an ``llvm.structured.alloca`` instrinsic.
Semantics:
""""""""""
@@ -27826,8 +27826,8 @@ Otherwise, the stack-allocated object that ``ptr`` points to is initially
marked as dead. After '``llvm.lifetime.start``', the stack object is marked as
alive and has an uninitialized value.
The stack object is marked as dead when either
-:ref:`llvm.lifetime.end <int_lifeend>` to the alloca is executed or the
-function returns.
+:ref:`llvm.lifetime.end <int_lifeend>` to the alloca/structured.alloca is
+executed or the function returns.
After :ref:`llvm.lifetime.end <int_lifeend>` is called,
'``llvm.lifetime.start``' on the stack object can be called again.
@@ -27855,7 +27855,8 @@ The '``llvm.lifetime.end``' intrinsic specifies the end of a
Arguments:
""""""""""
-The argument is either a pointer to an ``alloca`` instruction or a ``poison``
+The argument is either a pointer to an ``alloca`` instruction, a ``poison`` or
+an ``llvm.structured.alloca`` intrinsic.
value.
Semantics:
@@ -27866,7 +27867,8 @@ If ``ptr`` is a ``poison`` value, the intrinsic has no effect.
Otherwise, the stack-allocated object that ``ptr`` points to becomes dead after
the call to this intrinsic.
-Calling ``llvm.lifetime.end`` on an already dead alloca is no-op.
+Calling ``llvm.lifetime.end`` on an already dead alloca/structured.alloca is
+no-op.
'``llvm.invariant.start``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index e90462c15e6d5..785a7e11f6e98 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -1034,7 +1034,7 @@ def int_structured_gep
[LLVMMatchType<0>, llvm_vararg_ty],
[IntrNoMem, IntrSpeculatable]>;
-def int_structured_alloca : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [], []>;
+def int_structured_alloca : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [], [IntrInaccessibleMemOnly]>;
//===------------------- Standard C Library Intrinsics --------------------===//
//
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index be1aecce173fe..4dad2977a746c 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -7211,8 +7211,9 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end: {
Value *Ptr = Call.getArgOperand(0);
+ IntrinsicInst *II = dyn_cast<IntrinsicInst>(Ptr);
Check(isa<AllocaInst>(Ptr) || isa<PoisonValue>(Ptr) ||
- isa<IntrinsicInst>(Ptr),
+ (II && II->getIntrinsicID() == Intrinsic::structured_alloca),
"llvm.lifetime.start/end can only be used on alloca or poison",
&Call);
break;
>From 4042e3dba6ae89bf01b8d0384e1f0bd6d05e5df7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Thu, 26 Mar 2026 14:14:22 +0100
Subject: [PATCH 4/6] pr-feedback: mention alignment is targe-dependent
---
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 82bc76b414591..79b689baae2de 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -15349,8 +15349,8 @@ the stack for a logical type, such as an aggregate, an array, or a scalar.
Unlike the standard :ref:`alloca <i_alloca>` instruction, the physical memory
layout of a ``llvm.structured.alloca`` is completely opaque to the IR.
-Exact padding, size, and subtype offsets is target-dependent and may differ
-from the standard ``DataLayout``.
+Exact padding, size, alignment and subtype offsets is target-dependent and
+may differ from the standard ``DataLayout``.
Arguments:
""""""""""
>From 0653991ed7735ed824d94e5da273b9a1042d742a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Mon, 30 Mar 2026 15:29:09 +0200
Subject: [PATCH 5/6] pr-feedback: reorder docs lines
---
llvm/docs/LangRef.rst | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 79b689baae2de..538515ac01103 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -27924,8 +27924,8 @@ object's lifetime.
Arguments:
""""""""""
-The argument is either a pointer to an ``alloca`` instruction, a ``poison``
-value, or an ``llvm.structured.alloca`` instrinsic.
+The argument is either a pointer to an ``alloca`` instruction or an
+``llvm.structured.alloca`` instrinsic, or a ``poison`` value.
Semantics:
""""""""""
@@ -27965,9 +27965,8 @@ The '``llvm.lifetime.end``' intrinsic specifies the end of a
Arguments:
""""""""""
-The argument is either a pointer to an ``alloca`` instruction, a ``poison`` or
-an ``llvm.structured.alloca`` intrinsic.
-value.
+The argument is either a pointer to an ``alloca`` instruction or an
+``llvm.structured.alloca`` instrinsic, or a ``poison`` value.
Semantics:
""""""""""
>From b3c040950cadcd51a6d5e2acd043e154034c9f42 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Mon, 30 Mar 2026 16:41:39 +0200
Subject: [PATCH 6/6] pr-feedback: as test
---
llvm/test/Verifier/structured-alloca.ll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/test/Verifier/structured-alloca.ll b/llvm/test/Verifier/structured-alloca.ll
index 5400de49c64d9..64b4bb0c88b56 100644
--- a/llvm/test/Verifier/structured-alloca.ll
+++ b/llvm/test/Verifier/structured-alloca.ll
@@ -1,4 +1,4 @@
-; RUN: llvm-as -disable-output %s
+; RUN: llvm-as < %s | llvm-dis
%S = type { i32, i32 }
More information about the llvm-commits
mailing list