[llvm] [LLVM][Coroutines] Introduce TBAA metadata for coro frame object (PR #176543)

via llvm-commits llvm-commits at lists.llvm.org
Wed Jan 21 23:32:38 PST 2026


https://github.com/yonillasky updated https://github.com/llvm/llvm-project/pull/176543

>From bbf5aceea26d72dba0a1ce279082833cc4db2f97 Mon Sep 17 00:00:00 2001
From: Yoni Lavi <yoni.lavi at nextsilicon.com>
Date: Sat, 17 Jan 2026 10:26:39 +0200
Subject: [PATCH] LLVM, Coroutines: Introduce TBAA metadata for frame slot
 access

- The TBAA tags for loads from frame slots enable subsequent
  alias analysis to determine NoAlias between these and other
  memory accessed by the code within the coroutine itself.
- TBAA tags for different coroutines are distinct, since their
  scalar slots are mutually NoAlias as well.
- This allows subsequent passes, such as LICM, to hoist
  expensive reloading from frame memory out of tight loops
- The annotation is NOT added to allocas and the promise
  field, since it is possible to create aliasing accesses
  to them from "application code".
---
 llvm/include/llvm/IR/Metadata.h               |  1 +
 llvm/lib/IR/Metadata.cpp                      |  8 ++
 llvm/lib/Transforms/Coroutines/CoroFrame.cpp  | 25 ++++++-
 .../Coroutines/coro-split-tbaa-md-neg-2.ll    | 50 +++++++++++++
 .../Coroutines/coro-split-tbaa-md-neg.ll      | 74 +++++++++++++++++++
 .../Coroutines/coro-split-tbaa-md.ll          | 62 ++++++++++++++++
 6 files changed, 217 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/Transforms/Coroutines/coro-split-tbaa-md-neg-2.ll
 create mode 100644 llvm/test/Transforms/Coroutines/coro-split-tbaa-md-neg.ll
 create mode 100644 llvm/test/Transforms/Coroutines/coro-split-tbaa-md.ll

diff --git a/llvm/include/llvm/IR/Metadata.h b/llvm/include/llvm/IR/Metadata.h
index 85a7f8fd373c0..5fee796e0f6a2 100644
--- a/llvm/include/llvm/IR/Metadata.h
+++ b/llvm/include/llvm/IR/Metadata.h
@@ -734,6 +734,7 @@ class MDString : public Metadata {
   static MDString *get(LLVMContext &Context, const char *Str) {
     return get(Context, Str ? StringRef(Str) : StringRef());
   }
+  LLVM_ABI static MDString *getIfExists(LLVMContext &Context, StringRef Str);
 
   LLVM_ABI StringRef getString() const;
 
diff --git a/llvm/lib/IR/Metadata.cpp b/llvm/lib/IR/Metadata.cpp
index 326abd87ddae9..c692a621b7b81 100644
--- a/llvm/lib/IR/Metadata.cpp
+++ b/llvm/lib/IR/Metadata.cpp
@@ -621,6 +621,14 @@ MDString *MDString::get(LLVMContext &Context, StringRef Str) {
   return &MapEntry;
 }
 
+MDString *MDString::getIfExists(LLVMContext &Context, StringRef Str) {
+  auto &Store = Context.pImpl->MDStringCache;
+  auto I = Store.find(Str);
+  if (I == Store.end())
+    return nullptr;
+  return &I->getValue();
+}
+
 StringRef MDString::getString() const {
   assert(Entry && "Expected to find string map entry");
   return Entry->first();
diff --git a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
index 10be70c1eba79..4508112171b60 100644
--- a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
@@ -25,6 +25,7 @@
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/InstIterator.h"
 #include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/MDBuilder.h"
 #include "llvm/IR/Module.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/Debug.h"
@@ -1055,6 +1056,20 @@ static void insertSpills(const FrameDataInfo &FrameData, coro::Shape &Shape) {
   DominatorTree DT(*F);
   SmallDenseMap<Argument *, AllocaInst *, 4> ArgToAllocaMap;
 
+  MDBuilder MDB(C);
+  // Create a TBAA tag for accesses to certain coroutine frame slots, so that
+  // subsequent alias analysis will understand they do not intersect with
+  // user memory.
+  // We do this only if a suitable TBAA root already exists in the module.
+  MDNode *TBAATag = nullptr;
+  if (auto *CppTBAAStr = MDString::getIfExists(C, "Simple C++ TBAA")) {
+    auto *TBAARoot = MDNode::getIfExists(C, CppTBAAStr);
+    // Create a "fake" scalar type; all other types defined in the source
+    // language will be assumed non-aliasing with this type.
+    MDNode *Scalar = MDB.createTBAAScalarTypeNode(
+        (F->getName() + ".Frame Slot").str(), TBAARoot);
+    TBAATag = MDB.createTBAAStructTagNode(Scalar, Scalar, 0);
+  }
   for (auto const &E : FrameData.Spills) {
     Value *Def = E.first;
     auto SpillAlignment = Align(FrameData.getAlign(Def));
@@ -1098,12 +1113,16 @@ static void insertSpills(const FrameDataInfo &FrameData, coro::Shape &Shape) {
 
         auto *GEP = createGEPToFramePointer(FrameData, Builder, Shape, E.first);
         GEP->setName(E.first->getName() + Twine(".reload.addr"));
-        if (ByValTy)
+        if (ByValTy) {
           CurrentReload = GEP;
-        else
-          CurrentReload = Builder.CreateAlignedLoad(
+        } else {
+          auto *LI = Builder.CreateAlignedLoad(
               FrameTy->getElementType(FrameData.getFieldIndex(E.first)), GEP,
               SpillAlignment, E.first->getName() + Twine(".reload"));
+          if (TBAATag)
+            LI->setMetadata(LLVMContext::MD_tbaa, TBAATag);
+          CurrentReload = LI;
+        }
 
         TinyPtrVector<DbgVariableRecord *> DVRs = findDVRDeclares(Def);
         // Try best to find dbg.declare. If the spill is a temp, there may not
diff --git a/llvm/test/Transforms/Coroutines/coro-split-tbaa-md-neg-2.ll b/llvm/test/Transforms/Coroutines/coro-split-tbaa-md-neg-2.ll
new file mode 100644
index 0000000000000..d34a6f98bf512
--- /dev/null
+++ b/llvm/test/Transforms/Coroutines/coro-split-tbaa-md-neg-2.ll
@@ -0,0 +1,50 @@
+; A negative test to check coro-split does not generate TBAA metadata on frame access
+; if a suitable TBAA root is not found.
+; RUN: opt < %s -passes='cgscc(coro-split),simplifycfg,early-cse' -S | FileCheck %s
+
+; CHECK-NOT: f.Frame Slot
+define ptr @f(i32 %x) presplitcoroutine {
+entry:
+  %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)
+  %need.alloc = call i1 @llvm.coro.alloc(token %id)
+  br i1 %need.alloc, label %dyn.alloc, label %begin
+
+dyn.alloc:
+  %size = call i32 @llvm.coro.size.i32()
+  %alloc = call ptr @malloc(i32 %size)
+  br label %begin
+
+begin:
+  %phi = phi ptr [ null, %entry ], [ %alloc, %dyn.alloc ]
+  %hdl = call ptr @llvm.coro.begin(token %id, ptr %phi)
+  call void @print(i32 0)
+  %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+  switch i8 %0, label %suspend [i8 0, label %resume
+                                i8 1, label %cleanup]
+resume:
+  call void @print(i32 %x)
+  br label %cleanup
+
+cleanup:
+  %mem = call ptr @llvm.coro.free(token %id, ptr %hdl)
+  call void @free(ptr %mem)
+  br label %suspend
+suspend:
+  call void @llvm.coro.end(ptr %hdl, i1 0, token none)
+  ret ptr %hdl
+}
+
+declare ptr @llvm.coro.free(token, ptr)
+declare i32 @llvm.coro.size.i32()
+declare i8  @llvm.coro.suspend(token, i1)
+declare void @llvm.coro.resume(ptr)
+declare void @llvm.coro.destroy(ptr)
+
+declare token @llvm.coro.id(i32, ptr, ptr, ptr)
+declare i1 @llvm.coro.alloc(token)
+declare ptr @llvm.coro.begin(token, ptr)
+declare void @llvm.coro.end(ptr, i1, token)
+
+declare noalias ptr @malloc(i32) allockind("alloc,uninitialized") "alloc-family"="malloc"
+declare void @print(i32)
+declare void @free(ptr) willreturn allockind("free") "alloc-family"="malloc"
diff --git a/llvm/test/Transforms/Coroutines/coro-split-tbaa-md-neg.ll b/llvm/test/Transforms/Coroutines/coro-split-tbaa-md-neg.ll
new file mode 100644
index 0000000000000..615546ad05f9b
--- /dev/null
+++ b/llvm/test/Transforms/Coroutines/coro-split-tbaa-md-neg.ll
@@ -0,0 +1,74 @@
+; A negative test to check coro-split does not generate TBAA metadata on frame access
+; in cases it should not:
+; - Access to the promise slot
+; - Access to alloca slots
+; - Access to internally managed slots, such as the suspend point index
+; RUN: opt < %s -passes='cgscc(coro-split),simplifycfg,early-cse' -S | FileCheck %s
+
+; CHECK-NOT: f.Frame Slot
+
+ at g_x1 = global i32 1
+
+define ptr @f() presplitcoroutine {
+entry:
+  %promise = alloca i32
+  %buffer = alloca [128 x i8]
+  %x1.1 = load i32, ptr @g_x1, !tbaa !3
+  %ref1 = getelementptr inbounds [128 x i8], ptr %buffer, i32 0, i32 %x1.1
+  store i8 42, ptr %ref1
+  %id = call token @llvm.coro.id(i32 0, ptr %promise, ptr null, ptr null)
+  %need.alloc = call i1 @llvm.coro.alloc(token %id)
+  br i1 %need.alloc, label %dyn.alloc, label %begin
+
+dyn.alloc:
+  %size = call i32 @llvm.coro.size.i32()
+  %alloc = call ptr @malloc(i32 %size)
+  br label %begin
+
+begin:
+  %phi = phi ptr [ null, %entry ], [ %alloc, %dyn.alloc ]
+  %hdl = call ptr @llvm.coro.begin(token %id, ptr %phi)
+  store i32 111, ptr %promise
+  call void @print(i32 0)
+  %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+  switch i8 %0, label %suspend [i8 0, label %resume
+                                i8 1, label %cleanup]
+resume:
+  %the_111_from_earlier = load i32, ptr %promise, !tbaa !3
+  call void @print(i32 %the_111_from_earlier)
+  %x1.2 = load i32, ptr @g_x1, !tbaa !3
+  %ref2 = getelementptr inbounds [128 x i8], ptr %buffer, i32 0, i32 %x1.2
+  %result = load i32, ptr %ref2, !tbaa !3
+  store i32 %result, ptr %promise, !tbaa !3
+  %1 = call i8 @llvm.coro.suspend(token none, i1 true)
+  switch i8 %1, label %suspend [i8 1, label %cleanup]
+
+cleanup:
+  %mem = call ptr @llvm.coro.free(token %id, ptr %hdl)
+  call void @free(ptr %mem)
+  br label %suspend
+suspend:
+  call void @llvm.coro.end(ptr %hdl, i1 0, token none)
+  ret ptr %hdl
+}
+
+!0 = !{!"Simple C++ TBAA"}
+!1 = !{!"omnipotent char", !0, i64 0}
+!2 = !{!"int", !1, i64 0}
+!3 = !{!2, !2, i64 0}
+
+
+declare ptr @llvm.coro.free(token, ptr)
+declare i32 @llvm.coro.size.i32()
+declare i8  @llvm.coro.suspend(token, i1)
+declare void @llvm.coro.resume(ptr)
+declare void @llvm.coro.destroy(ptr)
+
+declare token @llvm.coro.id(i32, ptr, ptr, ptr)
+declare i1 @llvm.coro.alloc(token)
+declare ptr @llvm.coro.begin(token, ptr)
+declare void @llvm.coro.end(ptr, i1, token)
+
+declare noalias ptr @malloc(i32) allockind("alloc,uninitialized") "alloc-family"="malloc"
+declare void @print(i32)
+declare void @free(ptr) willreturn allockind("free") "alloc-family"="malloc"
diff --git a/llvm/test/Transforms/Coroutines/coro-split-tbaa-md.ll b/llvm/test/Transforms/Coroutines/coro-split-tbaa-md.ll
new file mode 100644
index 0000000000000..7eadf089d0cc0
--- /dev/null
+++ b/llvm/test/Transforms/Coroutines/coro-split-tbaa-md.ll
@@ -0,0 +1,62 @@
+; Tests that coro-split pass generates TBAA metadata on coroutine frame slot reloads.
+; RUN: opt < %s -passes='cgscc(coro-split),simplifycfg,early-cse' -S | FileCheck %s
+
+; CHECK-LABEL: @f.resume(
+; CHECK-SAME: %[[HDL:[A-Za-z0-9_]+]]
+define ptr @f(ptr %p) presplitcoroutine {
+entry:
+  %x = load i32, ptr %p, !tbaa !3
+  %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)
+  %need.alloc = call i1 @llvm.coro.alloc(token %id)
+  br i1 %need.alloc, label %dyn.alloc, label %begin
+
+dyn.alloc:
+  %size = call i32 @llvm.coro.size.i32()
+  %alloc = call ptr @malloc(i32 %size)
+  br label %begin
+
+begin:
+  %phi = phi ptr [ null, %entry ], [ %alloc, %dyn.alloc ]
+  %hdl = call ptr @llvm.coro.begin(token %id, ptr %phi)
+  call void @print(i32 0)
+  %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+  switch i8 %0, label %suspend [i8 0, label %resume
+                                i8 1, label %cleanup]
+resume:
+  call void @print(i32 %x)
+  ; CHECK: %[[X_RELOAD_ADDR:.+]] = getelementptr inbounds %f.Frame, ptr %[[HDL]], i32 0, i32 {{[0-9]+}}
+  ; CHECK: %{{.+}} = load i32, ptr %[[X_RELOAD_ADDR]], align 4, !tbaa ![[COROUTINE_SLOT_TAG:[0-9]+]]
+  br label %cleanup
+
+cleanup:
+  %mem = call ptr @llvm.coro.free(token %id, ptr %hdl)
+  call void @free(ptr %mem)
+  br label %suspend
+suspend:
+  call void @llvm.coro.end(ptr %hdl, i1 0, token none)
+  ret ptr %hdl
+}
+
+; CHECK: ![[COROUTINE_SLOT_ROOT:.+]] = !{!"Simple C++ TBAA"}
+; CHECK: ![[COROUTINE_SLOT_TAG]] = !{![[COROUTINE_SLOT_SCALAR:[0-9]+]], ![[COROUTINE_SLOT_SCALAR]], i64 0}
+; CHECK: ![[COROUTINE_SLOT_SCALAR]] = !{!"f.Frame Slot", ![[COROUTINE_SLOT_ROOT]], i64 0}
+!0 = !{!"Simple C++ TBAA"}
+!1 = !{!"omnipotent char", !0, i64 0}
+!2 = !{!"int", !1, i64 0}
+!3 = !{!2, !2, i64 0}
+
+
+declare ptr @llvm.coro.free(token, ptr)
+declare i32 @llvm.coro.size.i32()
+declare i8  @llvm.coro.suspend(token, i1)
+declare void @llvm.coro.resume(ptr)
+declare void @llvm.coro.destroy(ptr)
+
+declare token @llvm.coro.id(i32, ptr, ptr, ptr)
+declare i1 @llvm.coro.alloc(token)
+declare ptr @llvm.coro.begin(token, ptr)
+declare void @llvm.coro.end(ptr, i1, token)
+
+declare noalias ptr @malloc(i32) allockind("alloc,uninitialized") "alloc-family"="malloc"
+declare void @print(i32)
+declare void @free(ptr) willreturn allockind("free") "alloc-family"="malloc"



More information about the llvm-commits mailing list