Since the removal of typed pointers, there has been increasingly little meaning to the alloca element type, and some passes that cannot accurately preserve this element type at all. There are many parallels here between GEP vs ptradd, under the opaque pointer scheme, so I don’t want to repeat all of that discussion and will instead point to https://discourse.llvm.org/t/rfc-replacing-getelementptr-with-ptradd/68699 and https://discourse.llvm.org/t/rfc-de-type-ification-of-llvm-ir-why/88257. Notably, the mere existence of the alloca element type adds awkward complications to passes like SROA and MemCpyOpt, even though they just fallback to byte arrays anyways, so byte array support is already required everywhere. I would like to propose that LLVM discard this legacy function in favor of making an alloca simply a byte-sized object (actually an optional, preserving scalable ability, to be precise). In legacy terms, I propose that all alloca would become arrays like so: alloca i8, i64 %N or alloca < 1 x i8 x vscale>, i64 imm %N, with getAllocatedType returning i8 (until deleted) and isArrayAllocation returning true (until deleted). The proposed IR would be something like alloca i64 %N, align M or alloca vscale x i64 <constant>, align N, once the transition period is complete.
This RFC is intended to draw awareness towards this specific aspect of IR evolution, and the current perceived status of the work.
The reason is as follows: there seems to be no remaining semantic meaning to the alloca->getAllocatedType() function, as any semantic meaning would have inhibited SROA and MemCpyOpt from operating in many important cases. Instead, all semantic information is contained in the Load and Store instructions used, and in TBAA metadata if defined. Thus all optimization passes are supposed to disregard the type info from alloca, other than to compute its size. Indeed, often there is not even a way to preserve the original answer to that query in any meaningful way because of conflicting answers. Thus, across the whole LLVM codebase, there are not many uses of this function left. I’ve recently worked to further shrink that list (making Claude do a lot of the basic refactoring work). Now that almost all of that is done, I’ve cataloged all the remaining uses (Claude made the initial list format and chose to use emojis, but I reviewed and corrected it). The uses fall into 4 general categories:
- Simple structural queries that simply get deprecated directly and deleted when
getAllocatedTypeis removed. - Trivially removable queries that get replaced by the allocated size simply.
- Passes that try to derive some semantic meaning from the alloca type, which is therefore usually inconsistent with the langref in some way (e.g. attempts to assign a meaning to something which is not in strict accordance with the langref). This needs some case-by-case review and discussion to clarify what the intent is, and make that intent explicit in the langref and/or pass.
- GPU backends use that need more discussion & reviewers.
Some transition details:
IRBuilder: We already get the alignment from the DataLayout, so for the existing constructor, this seems it would be would be easy to get the TypeSize there too, and also emit a multiply with the ArraySize argument if needed. There would also be a new constructor needed that takes aValue *ArraySizeandbool vscale=false. There could also be a convenience function that takes aTypeSize.AllocaInst: The constructor would change to take a TypeSize instead of an element type.- Bitcode upgrade : Just like IRBuilder, this already uses the DataLayout for auto-upgrading the alignment, so this seems easy to rewrite there to a ConstantInt + optional-ArraySize-Multiply-Instruction + isScalableTy instruction flag.
Some questions:
- What does correct SPIRV and DXIL IR look like? There seems to be several possible options here, but it is not entirely clear which are supported most reliably by the respective backends. This likely shares most of the same issues as the ptradd transition.
- What is the correct resolution for each of the uses I called “problematic”? These are generally cases where it was unclear to me what semantics were being imposed here, so they may have a slightly different resolution depending on the original design intent. These are particularly the places where help is needed, either to suggest or review proposed changes.
Detailed File Analysis
1. Core IR Infrastructure
These are expected to be trivially removable, since they are non-semantic uses dealing with the structure of allocating IR.
These would probably change to just [ n x i8 x vscale ] or are simply deleted.
| Location | Usage | Description |
|---|---|---|
Instructions.h:122 |
Type *getAllocatedType() const { return AllocatedType; } |
API definition |
Instructions.cpp:66 |
TypeSize Size = DL.getTypeAllocSize(getAllocatedType()) |
Used with array size multiplication |
Instructions.cpp:4366 |
new AllocaInst(getAllocatedType(), ...) |
Clone operation |
Verifier.cpp:4700 |
Type *Ty = AI.getAllocatedType() |
Checking type properties |
Verifier.cpp:6239 |
!AI->getAllocatedType()->isPointerTy() |
Type property check |
IR/AsmWriter.cpp |
4740 | TypePrinter.print(AI->getAllocatedType(), Out) |
IR/Core.cpp |
3288 | wrap(unwrap<AllocaInst>(Alloca)->getAllocatedType()) |
IR/TypeFinder.cpp |
82 | incorporateType(AI->getAllocatedType()) |
Bitcode/Writer/ValueEnumerator.cpp |
482 | EnumerateType(AI->getAllocatedType()) |
Bitcode/Writer/BitcodeWriter.cpp |
3447 | Vals.push_back(VE.getTypeID(AI.getAllocatedType())) |
ValueMapper.cpp |
1056 | AI->setAllocatedType(TypeMapper->remapType(...)) |
SandboxIR/Instruction.cpp |
1414-1415 | Wrapper implementation |
SandboxIR/Instruction.h |
2310 | Wrapper declaration |
| Tests | 5267-5279 | Test code |
2. Trivially removable
These are uses that are just dealing with converting types to sizes, and so they get easily simplified once sizes are not based on types.
| Location | Line | Usage | Description |
|---|---|---|---|
IR/IRBuilder.cpp |
213 | TypeSize ElemSize = DL.getTypeAllocSize(AI->getAllocatedType()) |
Conversion helper |
Instruction.cpp:901 |
AI->getAllocatedType() == cast<AllocaInst>(I2)->getAllocatedType() |
Type equality check | |
FunctionComparator.cpp |
694-695 | cmpTypes(AI->getAllocatedType(), ...) |
Type comparison |
Evaluator.cpp |
346 | Type *Ty = AI->getAllocatedType() |
Creating a sized memory region |
Interpreter/Execution.cpp |
984 | Type *Ty = I.getAllocatedType() |
Type for allocation size |
MemoryTaggingSupport.cpp |
219-222 | Info.AI->getAllocatedType() for padding |
Creating a sized memory region |
SROA.cpp |
5176, 5238 | Type partitioning logic | Just needs sizes |
MemCpyOptimizer.cpp |
1706 | SrcAlloca->setAllocatedType(DestAlloca->getAllocatedType()) |
Just needs sizes |
AddressSanitizer.cpp |
1199, 1439 | isFixedSize computations | Just needs whether there is a static size |
InstCombineLoadStoreAlloca.cpp |
201 | ArrayType::get(AI.getAllocatedType(), C->getZExtValue()) |
Canonicalization |
ExpandVariadics.cpp |
760 | assert(Alloced->getAllocatedType() == VarargsTy) |
Type assertion |
SelectionDAG/SelectionDAGBuilder.cpp |
4621 | Type *Ty = I.getAllocatedType() |
Computing size for codegen |
GlobalISel/IRTranslator.cpp |
3200 | Type *Ty = AI.getAllocatedType() |
Computing size for codegen |
InlineCost.cpp |
1595 | Type *Ty = I.getAllocatedType() |
Type for size computation |
AMDGPULowerBufferFatPointers.cpp |
556 | Type *Ty = I.getAllocatedType() |
Type remapping |
AArch64ISelLowering.cpp |
30904 | AI->getAllocatedType()->isScalableTy() |
Type property check becomes getAllocationSize–>isScalable |
docs/OpaquePointers.rst |
Documentation | ||
docs/tutorial/MyFirstLanguageFrontend/LangImpl07.rst |
Tutorial | ||
examples/Kaleidoscope/Chapter7/toy.cpp |
Tutorial |
3. Problematic Passes
These each need to be addressed in some way before removing getAllocatedType. Each has something that seemed a bit complicated to untangle the intention:
| Location | Line | Usage | Description |
|---|---|---|---|
IR/DebugInfo.cpp |
2148 | DL.getTypeSizeInBits(AI->getAllocatedType()) |
Semantically awkward use of alloca size to determine what are all the debug information that might be attached |
Local.cpp |
1772, 1777 | AI->getAllocatedType()->isArrayTy(), ->isStructTy() |
Buggy debug_value handling: Proposed [Utils] Examine debug info type instead of alloca type to guess the debug behavior of the alloca uses by vtjnash · Pull Request #177480 · llvm/llvm-project · GitHub to preserve the behavior perhaps intended. There is a (very) long-term project to delete this code. |
AttributorAttributes.cpp |
7784 | return AI->getAllocatedType() |
Needs plan. One proposal: [RFC][Attributor] change argument privatization to size-based type selection by vtjnash · Pull Request #181716 · llvm/llvm-project · GitHub |
StackProtector.cpp |
504 | ContainsProtectableArray(AI->getAllocatedType(), ...) |
Applies stackprotector if there is an alloca defined to contain [ N x i8 ]. This needs to be a front-end job instead (e.g. deprecate ssp, and have the front end decide whether to use sspreq or not), since the LangRef uses the front-end term “Character arrays” to define this attribute, which is not a part of the LLVM IR lexicon. We actually hit this problem in Rust which uses alloca [N x i8] for everything, so the IR type heuristics are just completely broken. |
GCRootLowering.cpp |
181 | cast<PointerType>(Root->getAllocatedType()) |
The LangRef doesn’t exactly specify what operation happens here or how many bytes are permitted to be accessed. It could be a memset 0 or a nullptr in any addrspace, or the target addrspace could be added as an imm argument? It might make sense to add an elementtype attribute to the pointer argument. Not sure. |
StackSafetyAnalysis.cpp |
155 | DL.getTypeAllocSize(AI.getAllocatedType()) |
Mostly just computing the size, but returns the element size if the number is unknown, although that seems neither a necessary nor sufficient condition to ensure the number is not zero. So maybe this just gets deleted. |
MemoryBuiltins.cpp |
955, 1341, 1345 | Size calculations | Identical to StackSafetyAnalysis but with corrected handling of array size known estimates, making this somewhat difficult to model to preserve this primitive inference functionality exactly. |
clang/lib/CodeGen/CGObjCMac.cpp |
4334, 4347 | InlineAsm requirements | This inline asm site doesn’t have an assembly, but SelectionDAG seems to need this element type to decide which register could hold the entire contents of the alloca. |
SROA.cpp |
5176 | getTypePartition | llvm-project/llvm/lib/Transforms/Scalar/SROA.cpp at 8f378ea7e6fa31179266c69368be56a866b631e1 · llvm/llvm-project · GitHub the type here seems to influence the promotion strategy that is being used, and in particular which loads and stores are possible to compute an appropriate bitcast strategy for them once hoisted. The existence of FCA, and more significantly, the existence of pointer provenience / non-integral pointers can make this very tricky to make sure SROA doesn’t accidentally invent an illegal operation based on conflicting or confusing semantic information which might even be in dead code. |
4. In-Progress PRs
I’ve already merged many PRs, so there’s just a couple big ones that I still have open right now:
-
#178436 - [CodeGen] Use byte offsets and ptradd in ShadowStackGCLowering
- Covers:
llvm/lib/CodeGen/ShadowStackGCLowering.cpp:200
- Covers:
-
#178359 - [Coroutines] Replace struct alloca frame with byte array and ptradd
- Covers:
llvm/lib/Transforms/Coroutines/CoroFrame.cpp(5 uses)
- Covers:
-
#181845 -
OMPIRBuilder.cpp| 8 trivial uses, but a lot of tests to fix that relied on matching a typed GEP | Struct type access, Load types |
- Aside: I also made a proposed script for helping UTC to automatically annotate very large test matrixes like this backend uses, which is open for comments: [UTC] Add utility script to deal with conflicting RUN lines by vtjnash · Pull Request #182546 · llvm/llvm-project · GitHub
5. Problematic Backends
WebAssembly
This backend is currently awkwardly mutating the IR just before isel to fix a particular semantic issues from clang: there are two alloca addrspaces and some values (specifically, reference and function pointer types) can only be placed in one or the other, but currently clang cannot correctly choose which one to use, so instead LLVM reimplemented RAUW by hand to avoid the validator noticing that this is not a simple IR transform to do legally.
I have up a series of PRs to change the clang frontend to emit them to the correct alloca addrspace originally, so that LLVM can delete the IR mutation pass which currently relies on getAllocatedType. To wit:
If those changes are accepted, then that work culminates in:
If not acceptable, then we could instead inject more special cases to EmitParmDecl like other backends already do, but I’d prefer to eventually move those into Sema instead.
| File | Line | Usage |
|---|---|---|
WebAssemblyRefTypeMem2Local.cpp |
60, 64 | Type checks and cloning |
WebAssemblyFrameLowering.cpp |
72 | ComputeValueVTs(..., AI->getAllocatedType(), ...) |
AMDGPU
I think this seems to be smarter copy of the mem2reg/sroa pass, which can take an alloca accessed with non-constant GEP and rewrite it to a vector extractelement instead. I would hazard a guess that this probably does not need to initialize that pass info with the alloca type (consistent with the same change to SROA/mem2reg), since it then looks at all the loads and stores and make sure they have the same type as the alloca. It might even be an interesting pass to extract entirely from AMDGPU and make it its own LLVM pass (such as https://github.com/llvm/llvm-project/pull/165159)
| File | Usage |
|---|---|
AMDGPUPromoteAlloca.cpp |
analyzePromoteToVector |
SPIRV
There are many lines of code here to try to assign a possible load and store type to each offset in the alloca. Perhaps this could perhaps be shared with the other places that do the same analysis. How exactly to do that should perhaps be a separate discussion thread, since I know there’s a lot of work going on here already.
| File | Usage |
|---|---|
SPIRVEmitIntrinsics.cpp |
maybeAssignPtrType(..., Ref->getAllocatedType(), ...) |
DirectX
There’s probably a lot of different things going on here with getAllocatedType usage. One issue here is simply that it uses a copy of the LLVM 3.7 bitcode writer which needs something put in that field. Secondly there is code to rewrite alloca types to something simpler that DXIL can handle. Lastly, there is code to figure out what types are being used as loads and stores, similar to SPRIV. Similar to SPIRV, this might all break down once GEP becomes ptradd. But the inference from uses to alloca type might be able to be shared too between these backends, particularly once LLVM removes the last vestiges of typed GEP and typed alloca.