[llvm] [Coroutines] Support typed retcon allocators (PR #211099)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 22 02:28:17 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-ir
@llvm/pr-subscribers-llvm-transforms
Author: Karim Alweheshy (karim-alweheshy)
<details>
<summary>Changes</summary>
Continuation-based coroutine lowering currently assumes that the retcon
allocator has the shape `ptr (size)`. Swift's typed allocation entry point
uses `ptr (size, type-id)` so Swift-generated LLVM IR carries an optional
constant type identifier on `llvm.coro.id.retcon{.once}`.
Teach the retcon intrinsics to accept that optional identifier, validate the
one- and two-argument allocator forms, and forward the identifier when
emitting the frame allocation call. Auto-upgrade legacy fixed-six-argument
declarations and calls to the new variadic declaration so existing LLVM IR
and one-argument allocators keep their current behavior.
This is the upstream form of the compatibility change shipped by the Swift
LLVM fork in
[swiftlang/llvm-project@<!-- -->fe25ae6](https://github.com/swiftlang/llvm-project/commit/fe25ae6b55bd07f489faf24ebee66073d96eb126).
The new coroutine transform test covers the typed allocator form.
Local validation:
```
cmake --build build --target opt FileCheck -j8
build/bin/opt < llvm/test/Transforms/Coroutines/coro-retcon-typed-allocator.ll \
-passes='cgscc(coro-split),simplifycfg,early-cse' -S | \
build/bin/FileCheck llvm/test/Transforms/Coroutines/coro-retcon-typed-allocator.ll
cmake --build build --target check-llvm-transforms -j8
```
---
Full diff: https://github.com/llvm/llvm-project/pull/211099.diff
7 Files Affected:
- (modified) llvm/include/llvm/IR/Intrinsics.td (+2-2)
- (modified) llvm/include/llvm/Transforms/Coroutines/CoroInstr.h (+20-1)
- (modified) llvm/include/llvm/Transforms/Coroutines/CoroShape.h (+1)
- (modified) llvm/lib/IR/AutoUpgrade.cpp (+20)
- (modified) llvm/lib/Transforms/Coroutines/Coroutines.cpp (+20-4)
- (added) llvm/test/Transforms/Coroutines/coro-retcon-typed-allocator.ll (+30)
- (modified) llvm/test/Transforms/Coroutines/coro-retcon-unreachable.ll (+1-1)
``````````diff
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 37c9c783465d6..97b38f135fc85 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -1955,11 +1955,11 @@ def int_coro_id : DefaultAttrsIntrinsic<[llvm_token_ty],
NoCapture<ArgIndex<2>>]>;
def int_coro_id_retcon : Intrinsic<[llvm_token_ty],
[llvm_i32_ty, llvm_i32_ty, llvm_ptr_ty,
- llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty],
+ llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty, llvm_vararg_ty],
[]>;
def int_coro_id_retcon_once : Intrinsic<[llvm_token_ty],
[llvm_i32_ty, llvm_i32_ty, llvm_ptr_ty,
- llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty],
+ llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty, llvm_vararg_ty],
[]>;
def int_coro_alloc : Intrinsic<[llvm_i1_ty], [llvm_token_ty], []>;
def int_coro_id_async : Intrinsic<[llvm_token_ty],
diff --git a/llvm/include/llvm/Transforms/Coroutines/CoroInstr.h b/llvm/include/llvm/Transforms/Coroutines/CoroInstr.h
index 71632a46dcde1..f6fec26e069fa 100644
--- a/llvm/include/llvm/Transforms/Coroutines/CoroInstr.h
+++ b/llvm/include/llvm/Transforms/Coroutines/CoroInstr.h
@@ -236,7 +236,15 @@ class CoroIdInst : public AnyCoroIdInst {
/// This represents either the llvm.coro.id.retcon or
/// llvm.coro.id.retcon.once instruction.
class AnyCoroIdRetconInst : public AnyCoroIdInst {
- enum { SizeArg, AlignArg, StorageArg, PrototypeArg, AllocArg, DeallocArg };
+ enum {
+ SizeArg,
+ AlignArg,
+ StorageArg,
+ PrototypeArg,
+ AllocArg,
+ DeallocArg,
+ TypeIdArg
+ };
public:
LLVM_ABI void checkWellFormed() const;
@@ -271,6 +279,17 @@ class AnyCoroIdRetconInst : public AnyCoroIdInst {
getArgOperand(DeallocArg)->stripPointerCastsAndAliases());
}
+ /// Return the type ID to use when allocating typed memory.
+ ConstantInt *getTypeId() const {
+ if (arg_size() <= TypeIdArg)
+ return nullptr;
+ assert(hasTypeId() && "Invalid number of arguments");
+ return cast<ConstantInt>(getArgOperand(TypeIdArg));
+ }
+
+ /// Return whether a type ID is present in the argument list.
+ bool hasTypeId() const { return arg_size() == TypeIdArg + 1; }
+
// Methods to support type inquiry through isa, cast, and dyn_cast:
static bool classof(const IntrinsicInst *I) {
auto ID = I->getIntrinsicID();
diff --git a/llvm/include/llvm/Transforms/Coroutines/CoroShape.h b/llvm/include/llvm/Transforms/Coroutines/CoroShape.h
index 28931e3260e68..de7e8979506b4 100644
--- a/llvm/include/llvm/Transforms/Coroutines/CoroShape.h
+++ b/llvm/include/llvm/Transforms/Coroutines/CoroShape.h
@@ -120,6 +120,7 @@ struct Shape {
Function *Dealloc;
BasicBlock *ReturnBlock;
bool IsFrameInlineInStorage;
+ ConstantInt *TypeId;
};
struct AsyncLoweringStorage {
diff --git a/llvm/lib/IR/AutoUpgrade.cpp b/llvm/lib/IR/AutoUpgrade.cpp
index 926e18924956f..b81c9a7dd0116 100644
--- a/llvm/lib/IR/AutoUpgrade.cpp
+++ b/llvm/lib/IR/AutoUpgrade.cpp
@@ -1454,6 +1454,19 @@ static bool upgradeIntrinsicFunction1(Function *F, Function *&NewFn,
return true;
}
+ if (F->arg_size() == 6 && !F->isVarArg()) {
+ Intrinsic::ID ID =
+ StringSwitch<Intrinsic::ID>(Name)
+ .Case("coro.id.retcon", Intrinsic::coro_id_retcon)
+ .Case("coro.id.retcon.once", Intrinsic::coro_id_retcon_once)
+ .Default(Intrinsic::not_intrinsic);
+ if (ID != Intrinsic::not_intrinsic) {
+ rename(F);
+ NewFn = Intrinsic::getOrInsertDeclaration(F->getParent(), ID);
+ return true;
+ }
+ }
+
break;
}
case 'd':
@@ -5345,6 +5358,13 @@ void llvm::UpgradeIntrinsicCall(CallBase *CI, Function *NewFn) {
break;
}
+ case Intrinsic::coro_id_retcon:
+ case Intrinsic::coro_id_retcon_once: {
+ SmallVector<Value *, 6> Args(CI->args());
+ NewCall = Builder.CreateCall(NewFn, Args);
+ break;
+ }
+
case Intrinsic::vector_extract: {
StringRef Name = F->getName();
Name = Name.substr(5); // Strip llvm
diff --git a/llvm/lib/Transforms/Coroutines/Coroutines.cpp b/llvm/lib/Transforms/Coroutines/Coroutines.cpp
index a922099a1f43f..f56a44005256a 100644
--- a/llvm/lib/Transforms/Coroutines/Coroutines.cpp
+++ b/llvm/lib/Transforms/Coroutines/Coroutines.cpp
@@ -330,6 +330,7 @@ void coro::Shape::analyze(Function &F,
RetconLowering.Dealloc = ContinuationId->getDeallocFunction();
RetconLowering.ReturnBlock = nullptr;
RetconLowering.IsFrameInlineInStorage = false;
+ RetconLowering.TypeId = ContinuationId->getTypeId();
break;
}
default:
@@ -506,7 +507,12 @@ Value *coro::Shape::emitAlloc(IRBuilder<> &Builder, Value *Size,
Size = Builder.CreateIntCast(Size,
Alloc->getFunctionType()->getParamType(0),
/*is signed*/ false);
- auto *Call = Builder.CreateCall(Alloc, Size);
+ ConstantInt *TypeId = RetconLowering.TypeId;
+ CallInst *Call;
+ if (TypeId)
+ Call = Builder.CreateCall(Alloc, {Size, TypeId});
+ else
+ Call = Builder.CreateCall(Alloc, Size);
propagateCallAttrsFromCallee(Call, Alloc);
addCallToCallGraph(CG, Call, Alloc);
return Call;
@@ -599,9 +605,16 @@ static void checkWFAlloc(const Instruction *I, Value *V) {
if (!FT->getReturnType()->isPointerTy())
fail(I, "llvm.coro.* allocator must return a pointer", F);
- if (FT->getNumParams() != 1 ||
- !FT->getParamType(0)->isIntegerTy())
- fail(I, "llvm.coro.* allocator must take integer as only param", F);
+ if (FT->getNumParams() == 0 || FT->getNumParams() > 2)
+ fail(I, "llvm.coro.* allocator must take one or two parameters", F);
+
+ if (!FT->getParamType(0)->isIntegerTy())
+ fail(I, "llvm.coro.* allocator must take integer as its first parameter",
+ F);
+
+ if (FT->getNumParams() == 2 && !FT->getParamType(1)->isIntegerTy())
+ fail(I, "llvm.coro.* allocator must take integer as its second parameter",
+ F);
}
/// Check that the given value is a well-formed deallocator.
@@ -634,6 +647,9 @@ void AnyCoroIdRetconInst::checkWellFormed() const {
checkWFRetconPrototype(this, getArgOperand(PrototypeArg));
checkWFAlloc(this, getArgOperand(AllocArg));
checkWFDealloc(this, getArgOperand(DeallocArg));
+ if (hasTypeId())
+ checkConstantInt(this, getArgOperand(TypeIdArg),
+ "type ID argument to coro.id.retcon.* must be constant");
}
static void checkAsyncFuncPointer(const Instruction *I, Value *V) {
diff --git a/llvm/test/Transforms/Coroutines/coro-retcon-typed-allocator.ll b/llvm/test/Transforms/Coroutines/coro-retcon-typed-allocator.ll
new file mode 100644
index 0000000000000..4f4eb2cd901f5
--- /dev/null
+++ b/llvm/test/Transforms/Coroutines/coro-retcon-typed-allocator.ll
@@ -0,0 +1,30 @@
+; RUN: opt < %s -passes='cgscc(coro-split),simplifycfg,early-cse' -S | FileCheck %s
+
+define ptr @typed_allocator(ptr noalias dereferenceable(32) %buffer) presplitcoroutine {
+; CHECK-LABEL: @typed_allocator(
+; CHECK: call ptr @swift_coroFrameAlloc(i64 40, i64 123)
+entry:
+ %value = alloca [5 x i64], align 8
+ %id = call token (i32, i32, ptr, ptr, ptr, ptr, ...) @llvm.coro.id.retcon.once(i32 32, i32 8, ptr %buffer, ptr @prototype, ptr @swift_coroFrameAlloc, ptr @free, i64 123)
+ %handle = call ptr @llvm.coro.begin(token %id, ptr null)
+ call void @use(ptr %value)
+ %suspend = call i1 (...) @llvm.coro.suspend.retcon.i1()
+ br i1 %suspend, label %cleanup, label %resume
+
+resume:
+ call void @use(ptr %value)
+ br label %cleanup
+
+cleanup:
+ call void @llvm.coro.end(ptr %handle, i1 false, token none)
+ unreachable
+}
+
+declare void @prototype(ptr, i1 zeroext)
+declare ptr @swift_coroFrameAlloc(i64, i64)
+declare void @free(ptr)
+declare void @use(ptr)
+declare token @llvm.coro.id.retcon.once(i32, i32, ptr, ptr, ptr, ptr, ...)
+declare ptr @llvm.coro.begin(token, ptr)
+declare i1 @llvm.coro.suspend.retcon.i1(...)
+declare void @llvm.coro.end(ptr, i1, token)
diff --git a/llvm/test/Transforms/Coroutines/coro-retcon-unreachable.ll b/llvm/test/Transforms/Coroutines/coro-retcon-unreachable.ll
index 6e4a287e53b0a..80cbc40d60444 100644
--- a/llvm/test/Transforms/Coroutines/coro-retcon-unreachable.ll
+++ b/llvm/test/Transforms/Coroutines/coro-retcon-unreachable.ll
@@ -10,7 +10,7 @@ target datalayout = "E-p:64:64"
define hidden swiftcc { ptr, ptr } @no_suspends(ptr %buffer, i64 %arg) #1 {
; CHECK-LABEL: @no_suspends(
; CHECK-NEXT: AllocaSpillBB:
-; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id.retcon.once(i32 32, i32 8, ptr [[BUFFER:%.*]], ptr @prototype, ptr @malloc, ptr @free)
+; CHECK-NEXT: [[ID:%.*]] = call token (i32, i32, ptr, ptr, ptr, ptr, ...) @llvm.coro.id.retcon.once(i32 32, i32 8, ptr [[BUFFER:%.*]], ptr @prototype, ptr @malloc, ptr @free)
; CHECK-NEXT: call void @print(i64 [[ARG:%.*]])
; CHECK-NEXT: call void @llvm.trap()
; CHECK-NEXT: unreachable
``````````
</details>
https://github.com/llvm/llvm-project/pull/211099
More information about the llvm-commits
mailing list