[llvm] [IR][CodeGen] Specify llvm.gcroot contents are an opaque zero-initialized blob (PR #222333)

via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 9 06:57:34 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-x86

Author: Jameson Nash (vtjnash)

<details>
<summary>Changes</summary>

LangRef described a gcroot slot as holding a "root pointer", and said the intrinsic stores a null pointer into it. But nothing in LLVM needed to read the contents nor does anything in LLVM assign semantics to the meaning of alloca types. Update to say instead that the slot holds an opaque blob of arbitrary type describing a single gc-root slot, which the runtime collector may interpret however it likes, and that it is zero-initialized as a bit pattern rather than as a null value as legacy behavior (the code had already stated this is legacy behavior which should have been mentioned in the spec).

This change seems to be consistent with the behavior 2e59f142cc64 ("Allow llvm.gcroot to work with non-pointer allocas") was intending. However, it only changed the Verifier to relax the rejection added by bf40eee91a4e, and left LangRef saying the opposite. It also made the permission depend on the metadata argument being non-null, which is a collector's private tag and says nothing about the type of the root. Drop that check entirely (removing another misuse of getAllocatedType) and fix the documentation; this same contradiction is what #<!-- -->222223 proposes reverting #<!-- -->178436 over.

Now verify instead what the code generator actually requires: that the root is a static alloca. No existing gc-strategy handles anything else: ShadowStackGCLowering hoists roots into a frame it pushes in the entry block (which converts a dynamic allocation into static one), and SelectionDAGBuilder casts the root to a FrameIndexSDNode and aborts on anything but a fixed frame slot.

GCRootLowering's defensive initialization now memsets the whole allocation to zero instead of storing Constant::getNullValue() of the allocated type, which differs for a non-integral address space and missed all but the first element of `alloca T, i32 N`. A frontend that initializes the root itself keeps whatever it stored, which need not be the zero bit pattern. Between them, this and the Verifier change remove the last two uses of `AllocaInst->getAllocatedType` in the gcroot path.

ShadowStackGCLowering's frame map now holds the number of llvm.gcroot calls rather than the frame size in pointer-sized words; the two agreed only while every root was pointer-sized, and both GarbageCollection.md and the pass's own comment already call the field NumRoots.

fat.ll, added by bf40eee91a4e for the removed restriction, now checks that an aggregate root is accepted. ocaml-gc-assert.ll had its root alloca in a non-entry block and only survived that test because -O2 folded its constant switch before ISel -- defeating the intent of the test; it now gets an entry-block alloca and a non-constant switch, restoring the multi-block shape PR3168 was about. This test is still contradicted by the GarbageCollection.md docs, which claim the call also must be in the entry-block -- I've left that for future PRs to litigate that conflict.

---
Full diff: https://github.com/llvm/llvm-project/pull/222333.diff


9 Files Affected:

- (modified) llvm/docs/LangRef.md (+20-11) 
- (modified) llvm/lib/CodeGen/GCRootLowering.cpp (+33-14) 
- (modified) llvm/lib/CodeGen/ShadowStackGCLowering.cpp (+14-13) 
- (modified) llvm/lib/IR/Verifier.cpp (+2-6) 
- (modified) llvm/test/CodeGen/Generic/gc-lowering.ll (+32-4) 
- (modified) llvm/test/CodeGen/Generic/shadow-stack-gc-lowering.ll (+30-1) 
- (modified) llvm/test/CodeGen/X86/GC/fat.ll (+4-1) 
- (modified) llvm/test/CodeGen/X86/GC/ocaml-gc-assert.ll (+6-3) 
- (modified) llvm/test/Verifier/gcroot.ll (+37-3) 


``````````diff
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 14caff88243c1..a419cab003d16 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -14433,17 +14433,26 @@ the code generator, and allows some metadata to be associated with it.
 
 ##### Arguments:
 
-The first argument specifies the address of a stack object that contains
-the root pointer. The second pointer (which must be either a constant or
-a global value address) contains the meta-data to be associated with the
-root.
-
-##### Semantics:
-
-At runtime, a call to this intrinsic stores a null pointer into the
-"ptrloc" location. At compile-time, the code generator generates
-information to allow the runtime to find the pointer at GC safe points.
-The '`llvm.gcroot`' intrinsic may only be used in a function which
+The first argument specifies the address of a stack object holding the
+root, which must be a static `alloca`. LLVM attaches no meaning to the
+contents of that object: it is an opaque blob of arbitrary type which
+the runtime garbage collector may interpret in any way it likes. In
+particular it need not be, or contain, a pointer. The second pointer
+(which must be either a constant or a global value address) contains the
+meta-data to be associated with the root.
+
+##### Semantics:
+
+The frontend is responsible for initializing the "ptrloc" object before
+the first GC safe point is reached, since the collector may inspect it
+there. What it stores is up to the collector's conventions and need not
+be zero. As a defensive measure, a root which the code generator cannot
+see initialized in the entry block is zero-initialized at runtime: every
+byte of the object is set to zero. Note that this is a zero bit pattern,
+which is not necessarily the same as a null pointer value for the
+object's type. At compile-time, the code generator generates information
+to allow the runtime to find the object at GC safe points. The
+'`llvm.gcroot`' intrinsic may only be used in a function which
 {ref}`specifies a GC algorithm <gc>`.
 
 (int_gcread)=
diff --git a/llvm/lib/CodeGen/GCRootLowering.cpp b/llvm/lib/CodeGen/GCRootLowering.cpp
index 39c4802d6f58b..e8ff6206448d5 100644
--- a/llvm/lib/CodeGen/GCRootLowering.cpp
+++ b/llvm/lib/CodeGen/GCRootLowering.cpp
@@ -20,6 +20,7 @@
 #include "llvm/CodeGen/TargetRegisterInfo.h"
 #include "llvm/CodeGen/TargetSubtargetInfo.h"
 #include "llvm/IR/Dominators.h"
+#include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Module.h"
 #include "llvm/InitializePasses.h"
@@ -28,12 +29,12 @@
 using namespace llvm;
 
 /// Lower barriers out of existence (if the associated GCStrategy hasn't
-/// already done so...), and insert initializing stores to roots as a defensive
-/// measure.  Given we're going to report all roots live at all safepoints, we
-/// need to be able to ensure each root has been initialized by the point the
-/// first safepoint is reached.  This really should have been done by the
-/// frontend, but the old API made this non-obvious, so we do a potentially
-/// redundant store just in case.
+/// already done so...), and zero-initialize roots as a defensive measure.
+/// Given we're going to report all roots live at all safepoints, we need to be
+/// able to ensure each root has been initialized by the point the first
+/// safepoint is reached.  This really should have been done by the frontend,
+/// but the old API made this non-obvious, so we do a potentially redundant
+/// zeroing just in case.
 static bool DoLowering(Function &F, GCStrategy &S);
 
 namespace {
@@ -148,11 +149,13 @@ static bool CouldBecomeSafePoint(Instruction *I) {
       isa<LoadInst>(I))
     return false;
 
-  // llvm.gcroot is safe because it doesn't do anything at runtime.
+  // llvm.gcroot is safe because it doesn't do anything at runtime.  A memset
+  // is safe because it cannot collect; it is also how roots are initialized
+  // below, so exempting it keeps this pass idempotent.
   if (CallInst *CI = dyn_cast<CallInst>(I))
     if (Function *F = CI->getCalledFunction())
       if (Intrinsic::ID IID = F->getIntrinsicID())
-        if (IID == Intrinsic::gcroot)
+        if (IID == Intrinsic::gcroot || IID == Intrinsic::memset)
           return false;
 
   return true;
@@ -166,19 +169,35 @@ static bool InsertRootInitializers(Function &F, ArrayRef<AllocaInst *> Roots) {
 
   // Search for initializers in the initial BB.
   SmallPtrSet<AllocaInst *, 16> InitedRoots;
-  for (; !CouldBecomeSafePoint(&*IP); ++IP)
+  for (; !CouldBecomeSafePoint(&*IP); ++IP) {
+    Value *Dest = nullptr;
     if (StoreInst *SI = dyn_cast<StoreInst>(IP))
-      if (AllocaInst *AI =
-              dyn_cast<AllocaInst>(SI->getOperand(1)->stripPointerCasts()))
+      Dest = SI->getOperand(1);
+    else if (MemSetInst *MSI = dyn_cast<MemSetInst>(IP))
+      Dest = MSI->getDest();
+    if (Dest)
+      if (AllocaInst *AI = dyn_cast<AllocaInst>(Dest->stripPointerCasts()))
         InitedRoots.insert(AI);
+  }
 
-  // Add root initializers.
+  // Add root initializers.  The contents of a root are an opaque blob which
+  // only the collector understands, so all we can do is fill it with zeros;
+  // note that this is a zero bit pattern rather than a null value of the
+  // allocated type, which need not be the same thing.
+  const DataLayout &DL = F.getDataLayout();
   bool MadeChange = false;
 
   for (AllocaInst *Root : Roots)
     if (!InitedRoots.count(Root)) {
-      new StoreInst(Constant::getNullValue(Root->getAllocatedType()), Root,
-                    std::next(Root->getIterator()));
+      std::optional<TypeSize> RootSize = Root->getAllocationSize(DL);
+      if (!RootSize)
+        reportFatalUsageError(
+            "Intrinsic::gcroot requires a fixed size stack object");
+      IRBuilder<> Builder(&*std::next(Root->getIterator()));
+      Builder.CreateMemSet(
+          Root, Builder.getInt8(0),
+          Builder.CreateTypeSize(Builder.getInt64Ty(), *RootSize),
+          Root->getAlign());
       MadeChange = true;
     }
 
diff --git a/llvm/lib/CodeGen/ShadowStackGCLowering.cpp b/llvm/lib/CodeGen/ShadowStackGCLowering.cpp
index 2b35bcb80d628..a961eb2926c77 100644
--- a/llvm/lib/CodeGen/ShadowStackGCLowering.cpp
+++ b/llvm/lib/CodeGen/ShadowStackGCLowering.cpp
@@ -76,7 +76,7 @@ class ShadowStackGCLoweringImpl {
 
 private:
   bool IsNullValue(Value *V);
-  Constant *GetFrameMap(Function &F, uint64_t FrameSizeInPtrs);
+  Constant *GetFrameMap(Function &F);
   std::pair<uint64_t, Align> ComputeFrameLayout(Function &F);
   void CollectRoots(Function &F);
 };
@@ -140,8 +140,7 @@ FunctionPass *llvm::createShadowStackGCLoweringPass() { return new ShadowStackGC
 
 ShadowStackGCLowering::ShadowStackGCLowering() : FunctionPass(ID) {}
 
-Constant *ShadowStackGCLoweringImpl::GetFrameMap(Function &F,
-                                                 uint64_t FrameSizeInPtrs) {
+Constant *ShadowStackGCLoweringImpl::GetFrameMap(Function &F) {
   // doInitialization creates the abstract type of this value.
   Type *VoidPtr = PointerType::getUnqual(F.getContext());
 
@@ -159,7 +158,7 @@ Constant *ShadowStackGCLoweringImpl::GetFrameMap(Function &F,
   Type *Int32Ty = Type::getInt32Ty(F.getContext());
 
   Constant *BaseElts[] = {
-      ConstantInt::get(Int32Ty, FrameSizeInPtrs, false),
+      ConstantInt::get(Int32Ty, Roots.size(), false),
       ConstantInt::get(Int32Ty, NumMeta, false),
   };
 
@@ -249,7 +248,9 @@ bool ShadowStackGCLoweringImpl::doInitialization(Module &M) {
   //   void *Meta[];     // May be absent for roots without metadata.
   // };
   std::vector<Type *> EltTys;
-  // 32 bits is ok up to a 32GB stack frame. :)
+  // Number of calls to llvm.gcroot in the frame.  Note that the roots
+  // themselves are opaque blobs of arbitrary size, so this is a count of
+  // roots, not a measure of the frame's size.
   EltTys.push_back(Type::getInt32Ty(M.getContext()));
   // Specifies length of variable length array.
   EltTys.push_back(Type::getInt32Ty(M.getContext()));
@@ -326,9 +327,8 @@ bool ShadowStackGCLoweringImpl::runOnFunction(Function &F,
   // Compute frame layout using byte offsets first.
   auto [FrameSize, FrameAlign] = ComputeFrameLayout(F);
 
-  // Build the constant map with frame size in pointer-sized units.
-  uint64_t PtrSize = DL.getPointerSize();
-  Value *FrameMap = GetFrameMap(F, FrameSize / PtrSize - 2);
+  // Build the constant map describing the roots in this frame.
+  Value *FrameMap = GetFrameMap(F);
 
   // Build the shadow stack entry at the very start of the function.
   BasicBlock::iterator IP = F.getEntryBlock().begin();
@@ -346,6 +346,7 @@ bool ShadowStackGCLoweringImpl::runOnFunction(Function &F,
       AtEntry.CreateLoad(AtEntry.getPtrTy(), Head, "gc_currhead");
 
   // Map pointer is at offset PtrSize (after the Next pointer)
+  uint64_t PtrSize = DL.getPointerSize();
   Value *EntryMapPtr = AtEntry.CreatePtrAdd(
       StackEntry, AtEntry.getInt64(PtrSize), "gc_frame.map");
   AtEntry.CreateStore(FrameMap, EntryMapPtr);
@@ -385,11 +386,11 @@ bool ShadowStackGCLoweringImpl::runOnFunction(Function &F,
                          Align(1));
   }
 
-  // Move past the original stores inserted by GCStrategy::InitRoots. This isn't
-  // really necessary (the collector would never see the intermediate state at
-  // runtime), but it's nicer not to push the half-initialized entry onto the
-  // shadow stack.
-  while (isa<StoreInst>(IP))
+  // Move past the original zero-initialization inserted by
+  // GCStrategy::InitRoots. This isn't really necessary (the collector would
+  // never see the intermediate state at runtime), but it's nicer not to push
+  // the half-initialized entry onto the shadow stack.
+  while (isa<StoreInst>(IP) || isa<MemSetInst>(IP))
     ++IP;
   AtEntry.SetInsertPoint(IP->getParent(), IP);
 
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 26c34b4221fde..1064130baa045 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -6514,14 +6514,10 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
       auto *AI =
           dyn_cast<AllocaInst>(Call.getArgOperand(0)->stripPointerCasts());
       Check(AI, "llvm.gcroot parameter #1 must be an alloca.", Call);
+      Check(AI->isStaticAlloca(),
+            "llvm.gcroot parameter #1 must be a static alloca.", Call);
       Check(isa<Constant>(Call.getArgOperand(1)),
             "llvm.gcroot parameter #2 must be a constant.", Call);
-      if (!AI->getAllocatedType()->isPointerTy()) {
-        Check(!isa<ConstantPointerNull>(Call.getArgOperand(1)),
-              "llvm.gcroot parameter #1 must either be a pointer alloca, "
-              "or argument #2 must be a non-null constant.",
-              Call);
-      }
     }
 
     Check(Call.getParent()->getParent()->hasGC(),
diff --git a/llvm/test/CodeGen/Generic/gc-lowering.ll b/llvm/test/CodeGen/Generic/gc-lowering.ll
index 1c820d6934f51..dbcd8149bd1d6 100644
--- a/llvm/test/CodeGen/Generic/gc-lowering.ll
+++ b/llvm/test/CodeGen/Generic/gc-lowering.ll
@@ -11,9 +11,9 @@ define i32 @main() gc "shadow-stack" {
 ; CHECK-LABEL: define i32 @main() gc "shadow-stack" {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    [[A:%.*]] = alloca ptr, align 8
-; CHECK-NEXT:    store ptr null, ptr [[A]], align 8
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 8 [[A]], i8 0, i64 8, i1 false)
 ; CHECK-NEXT:    [[B:%.*]] = alloca ptr, align 8
-; CHECK-NEXT:    store ptr null, ptr [[B]], align 8
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 8 [[B]], i8 0, i64 8, i1 false)
 ; CHECK-NEXT:    call void @llvm_gc_initialize(i32 1048576)
 ; CHECK-NEXT:    call void @llvm.gcroot(ptr [[A]], ptr null)
 ; CHECK-NEXT:    [[APTR:%.*]] = call ptr @llvm_gc_allocate(i32 10)
@@ -67,9 +67,9 @@ define void @non_ptr_alloca_root() gc "shadow-stack" {
 ; CHECK-LABEL: define void @non_ptr_alloca_root() gc "shadow-stack" {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    %A = alloca double, align 8
-; CHECK-NEXT:    store double 0.000000e+00, ptr %A, align 8
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 8 %A, i8 0, i64 8, i1 false)
 ; CHECK-NEXT:    %B = alloca { double, double }, align 8
-; CHECK-NEXT:    store { double, double } zeroinitializer, ptr %B, align 8
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 8 %B, i8 0, i64 16, i1 false)
 ; CHECK-NEXT:    call void @llvm.gcroot(ptr %A, ptr @metadata)
 ; CHECK-NEXT:    call void @llvm.gcroot(ptr %B, ptr @metadata)
 ; CHECK-NEXT:    ret void
@@ -86,3 +86,31 @@ entry:
 
   ret void
 }
+
+; The root is an opaque blob, so a root that is neither a pointer nor of
+; pointer size is zero-initialized in full.
+define void @fat_and_array_roots() gc "shadow-stack" {
+; CHECK-LABEL: define void @fat_and_array_roots() gc "shadow-stack" {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    %A = alloca { ptr, i1 }, align 8
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 8 %A, i8 0, i64 16, i1 false)
+; CHECK-NEXT:    %B = alloca [4 x ptr], align 8
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 8 %B, i8 0, i64 32, i1 false)
+; CHECK-NEXT:    %C = alloca ptr, i32 7, align 8
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 8 %C, i8 0, i64 56, i1 false)
+; CHECK-NEXT:    call void @llvm.gcroot(ptr %A, ptr null)
+; CHECK-NEXT:    call void @llvm.gcroot(ptr %B, ptr null)
+; CHECK-NEXT:    call void @llvm.gcroot(ptr %C, ptr null)
+; CHECK-NEXT:    ret void
+; CHECK-NEXT:  }
+entry:
+  %A = alloca { ptr, i1 }
+  %B = alloca [4 x ptr]
+  %C = alloca ptr, i32 7
+
+  call void @llvm.gcroot(ptr %A, ptr null)
+  call void @llvm.gcroot(ptr %B, ptr null)
+  call void @llvm.gcroot(ptr %C, ptr null)
+
+  ret void
+}
diff --git a/llvm/test/CodeGen/Generic/shadow-stack-gc-lowering.ll b/llvm/test/CodeGen/Generic/shadow-stack-gc-lowering.ll
index 3df1147b19e52..2897084ef22fc 100644
--- a/llvm/test/CodeGen/Generic/shadow-stack-gc-lowering.ll
+++ b/llvm/test/CodeGen/Generic/shadow-stack-gc-lowering.ll
@@ -15,7 +15,8 @@ declare void @llvm.gcroot(ptr, ptr)
 ; CHECK: @__gc_two_roots = internal constant %gc_map.0.0 { %gc_map { i32 2, i32 0 }, [0 x ptr] zeroinitializer }
 ; CHECK: @__gc_root_with_metadata = internal constant %gc_map.1 { %gc_map { i32 1, i32 1 }, [1 x ptr] [ptr @type_tag] }
 ; CHECK: @__gc_mixed_metadata = internal constant %gc_map.1.1 { %gc_map { i32 2, i32 1 }, [1 x ptr] [ptr @type_tag] }
-; CHECK: @__gc_with_invoke = internal constant %gc_map.0.2 { %gc_map { i32 1, i32 0 }, [0 x ptr] zeroinitializer }
+; CHECK: @__gc_fat_root = internal constant %gc_map.0.2 { %gc_map { i32 2, i32 0 }, [0 x ptr] zeroinitializer }
+; CHECK: @__gc_with_invoke = internal constant %gc_map.0.3 { %gc_map { i32 1, i32 0 }, [0 x ptr] zeroinitializer }
 ;.
 define void @single_root(ptr %obj) gc "shadow-stack" {
 ; CHECK-LABEL: @single_root(
@@ -135,6 +136,34 @@ entry:
   ret void
 }
 
+; Roots are opaque blobs of arbitrary size, so the frame map's first field is
+; the number of llvm.gcroot calls, not the size of the frame: here two roots
+; occupy three pointers' worth of the frame, and NumRoots must still be 2.
+define void @fat_root(ptr %obj) gc "shadow-stack" {
+; CHECK-LABEL: @fat_root(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[GC_FRAME:%.*]] = alloca [40 x i8], align 8
+; CHECK-NEXT:    [[GC_CURRHEAD:%.*]] = load ptr, ptr @llvm_gc_root_chain, align 8
+; CHECK-NEXT:    [[GC_FRAME_MAP:%.*]] = getelementptr i8, ptr [[GC_FRAME]], i64 8
+; CHECK-NEXT:    store ptr @__gc_fat_root, ptr [[GC_FRAME_MAP]], align 8
+; CHECK-NEXT:    [[FAT:%.*]] = getelementptr i8, ptr [[GC_FRAME]], i64 16
+; CHECK-NEXT:    [[THIN:%.*]] = getelementptr i8, ptr [[GC_FRAME]], i64 32
+; CHECK-NEXT:    store ptr [[GC_CURRHEAD]], ptr [[GC_FRAME]], align 8
+; CHECK-NEXT:    store ptr [[GC_FRAME]], ptr @llvm_gc_root_chain, align 8
+; CHECK-NEXT:    store ptr [[OBJ:%.*]], ptr [[THIN]], align 8
+; CHECK-NEXT:    [[GC_SAVEDHEAD:%.*]] = load ptr, ptr [[GC_FRAME]], align 8
+; CHECK-NEXT:    store ptr [[GC_SAVEDHEAD]], ptr @llvm_gc_root_chain, align 8
+; CHECK-NEXT:    ret void
+;
+entry:
+  %fat = alloca { ptr, i1 }
+  %thin = alloca ptr
+  call void @llvm.gcroot(ptr %fat, ptr null)
+  call void @llvm.gcroot(ptr %thin, ptr null)
+  store ptr %obj, ptr %thin
+  ret void
+}
+
 ; A function with an invoke: the EscapeEnumerator must insert a shadow stack
 ; pop on the unwind path as well as on the normal return path.
 declare void @may_throw()
diff --git a/llvm/test/CodeGen/X86/GC/fat.ll b/llvm/test/CodeGen/X86/GC/fat.ll
index 9c1144f20e041..b4c880c636cef 100644
--- a/llvm/test/CodeGen/X86/GC/fat.ll
+++ b/llvm/test/CodeGen/X86/GC/fat.ll
@@ -1,4 +1,7 @@
-; RUN: not llvm-as < %s > /dev/null 2>&1
+; RUN: llvm-as < %s > /dev/null
+
+; The contents of a gc root are an opaque blob of arbitrary type and size, so
+; an aggregate root is accepted.
 
 declare void @llvm.gcroot(ptr, ptr) nounwind
 
diff --git a/llvm/test/CodeGen/X86/GC/ocaml-gc-assert.ll b/llvm/test/CodeGen/X86/GC/ocaml-gc-assert.ll
index bc3444f4914e0..eee7129d9ed72 100644
--- a/llvm/test/CodeGen/X86/GC/ocaml-gc-assert.ll
+++ b/llvm/test/CodeGen/X86/GC/ocaml-gc-assert.ll
@@ -3,11 +3,14 @@
 
 ; CHECK-LABEL: append
 
-define ptr @append() gc "ocaml" {
+; The root must be a static alloca; the llvm.gcroot call stays in L1, where the
+; original test had it.  The switch condition must not be a constant, or the
+; CFG folds away before ISel and this stops testing anything.
+define ptr @append(i32 %n) gc "ocaml" {
 entry:
-  switch i32 0, label %L2 [i32 0, label %L1]
-L1:
   %var8 = alloca ptr
+  switch i32 %n, label %L2 [i32 0, label %L1]
+L1:
   call void @llvm.gcroot(ptr %var8,ptr null)
   br label %L3
 L2:
diff --git a/llvm/test/Verifier/gcroot.ll b/llvm/test/Verifier/gcroot.ll
index 1d0e6b59b0cd0..0d70dd7778b40 100644
--- a/llvm/test/Verifier/gcroot.ll
+++ b/llvm/test/Verifier/gcroot.ll
@@ -18,10 +18,21 @@ define void @must_be_alloca() gc "test" {
   ret void
 }
 
-define void @non_ptr_alloca_null() gc "test" {
-  ; CHECK: llvm.gcroot parameter #1 must either be a pointer alloca, or argument #2 must be a non-null constant.
+define void @vla_alloca(i32 %n) gc "test" {
+  ; CHECK: llvm.gcroot parameter #1 must be a static alloca.
   ; CHECK-NEXT: call void @llvm.gcroot(ptr %alloca, ptr null)
-  %alloca = alloca i32
+  %alloca = alloca ptr, i32 %n
+  call void @llvm.gcroot(ptr %alloca, ptr null)
+  ret void
+}
+
+define void @non_entry_block_alloca() gc "test" {
+  ; CHECK: llvm.gcroot parameter #1 must be a static alloca.
+  ; CHECK-NEXT: call void @llvm.gcroot(ptr %alloca, ptr null)
+entry:
+  br label %next
+next:
+  %alloca = alloca ptr
   call void @llvm.gcroot(ptr %alloca, ptr null)
   ret void
 }
@@ -34,6 +45,8 @@ define void @non_constant_arg1(ptr %arg) gc "test" {
   ret void
 }
 
+; The root is an opaque blob of arbitrary type and size, so a non-pointer
+; alloca is fine, with or without metadata.
 define void @non_ptr_alloca_non_null() gc "test" {
 ; CHECK-NOT: llvm.gcroot parameter
   %alloca = alloca i32
@@ -41,6 +54,27 @@ define void @non_ptr_alloca_non_null() gc "test" {
   ret void
 }
 
+define void @non_ptr_alloca_null() gc "test" {
+; CHECK-NOT: llvm.gcroot parameter
+  %alloca = alloca i32
+  call void @llvm.gcroot(ptr %alloca, ptr null)
+  ret void
+}
+
+define void @aggregate_alloca_null() gc "test" {
+; CHECK-NOT: llvm.gcroot parameter
+  %alloca = alloca { ptr, i1 }
+  call void @llvm.gcroot(ptr %alloca, ptr null)
+  ret void
+}
+
+define void @constant_size_array_alloca() gc "test" {
+; CHECK-NOT: llvm.gcroot parameter
+  %alloca = alloca ptr, i32 7
+  call void @llvm.gcroot(ptr %alloca, ptr null)
+  ret void
+}
+
 define void @casted_alloca() gc "test" {
 ; CHECK-NOT: llvm.gcroot parameter
   %alloca = alloca ptr

``````````

</details>


https://github.com/llvm/llvm-project/pull/222333


More information about the llvm-commits mailing list