[llvm] [LLVM][Coroutines] Introduce TBAA metadata for coro frame object (PR #176543)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jan 22 08:27:08 PST 2026
https://github.com/yonillasky updated https://github.com/llvm/llvm-project/pull/176543
>From 082c9f803072877c7afed42e01b315c644abb00a 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 | 24 +++++-
.../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, 216 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 539f5178245fd..9bd7e0b48c2d3 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"
@@ -1085,6 +1086,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;
Type *ByValTy = extractByvalIfArgument(Def);
@@ -1105,13 +1120,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 {
+ } else {
auto SpillAlignment = Align(FrameData.getAlign(Def));
- CurrentReload = Builder.CreateAlignedLoad(
+ 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);
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