[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