Skip to content

Conversation

@TylerNowicki
Copy link
Collaborator

  • Split buildCoroutineFrame into code related to normalization and code related to actually building the coroutine frame.
  • This will enable future specialization of buildCoroutineFrame for different ABIs while the normalization can be done by splitCoroutine prior to calling buildCoroutineFrame.

See RFC for more info: https://discourse.llvm.org/t/rfc-abi-objects-for-coroutines/81057

* Split buildCoroutineFrame into code related to normalization and code
  related to actually building the coroutine frame.
* This will enable future specialization of buildCoroutineFrame for
  different ABIs.
@llvmbot
Copy link
Member

llvmbot commented Sep 10, 2024

@llvm/pr-subscribers-llvm-transforms

Author: Tyler Nowicki (TylerNowicki)

Changes
  • Split buildCoroutineFrame into code related to normalization and code related to actually building the coroutine frame.
  • This will enable future specialization of buildCoroutineFrame for different ABIs while the normalization can be done by splitCoroutine prior to calling buildCoroutineFrame.

See RFC for more info: https://discourse.llvm.org/t/rfc-abi-objects-for-coroutines/81057


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

3 Files Affected:

  • (modified) llvm/lib/Transforms/Coroutines/CoroFrame.cpp (+8-6)
  • (modified) llvm/lib/Transforms/Coroutines/CoroInternal.h (+3-1)
  • (modified) llvm/lib/Transforms/Coroutines/CoroSplit.cpp (+2-1)
diff --git a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
index 4b76fc79361008..8ee4bfa3b888df 100644
--- a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
@@ -1754,7 +1754,8 @@ static bool willLeaveFunctionImmediatelyAfter(BasicBlock *BB,
   if (depth == 0) return false;
 
   // If this is a suspend block, we're about to exit the resumption function.
-  if (isSuspendBlock(BB)) return true;
+  if (isSuspendBlock(BB))
+    return true;
 
   // Recurse into the successors.
   for (auto *Succ : successors(BB)) {
@@ -2288,9 +2289,8 @@ static void doRematerializations(
   rewriteMaterializableInstructions(AllRemats);
 }
 
-void coro::buildCoroutineFrame(
-    Function &F, Shape &Shape, TargetTransformInfo &TTI,
-    const std::function<bool(Instruction &)> &MaterializableCallback) {
+void coro::normalizeCoroutine(Function &F, coro::Shape &Shape,
+                              TargetTransformInfo &TTI) {
   // Don't eliminate swifterror in async functions that won't be split.
   if (Shape.ABI != coro::ABI::Async || !Shape.CoroSuspends.empty())
     eliminateSwiftError(F, Shape);
@@ -2337,10 +2337,12 @@ void coro::buildCoroutineFrame(
   // Transforms multi-edge PHI Nodes, so that any value feeding into a PHI will
   // never have its definition separated from the PHI by the suspend point.
   rewritePHIs(F);
+}
 
-  // Build suspend crossing info.
+void coro::buildCoroutineFrame(
+    Function &F, Shape &Shape,
+    const std::function<bool(Instruction &)> &MaterializableCallback) {
   SuspendCrossingInfo Checker(F, Shape.CoroSuspends, Shape.CoroEnds);
-
   doRematerializations(F, Checker, MaterializableCallback);
 
   const DominatorTree DT(F);
diff --git a/llvm/lib/Transforms/Coroutines/CoroInternal.h b/llvm/lib/Transforms/Coroutines/CoroInternal.h
index be86f96525b677..698c21a797420a 100644
--- a/llvm/lib/Transforms/Coroutines/CoroInternal.h
+++ b/llvm/lib/Transforms/Coroutines/CoroInternal.h
@@ -281,8 +281,10 @@ struct LLVM_LIBRARY_VISIBILITY Shape {
 };
 
 bool defaultMaterializable(Instruction &V);
+void normalizeCoroutine(Function &F, coro::Shape &Shape,
+                        TargetTransformInfo &TTI);
 void buildCoroutineFrame(
-    Function &F, Shape &Shape, TargetTransformInfo &TTI,
+    Function &F, Shape &Shape,
     const std::function<bool(Instruction &)> &MaterializableCallback);
 CallInst *createMustTailCall(DebugLoc Loc, Function *MustTailCallFn,
                              TargetTransformInfo &TTI,
diff --git a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
index 494c4d632de95f..dc3829d7f28eb1 100644
--- a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
@@ -2030,7 +2030,8 @@ splitCoroutine(Function &F, SmallVectorImpl<Function *> &Clones,
   lowerAwaitSuspends(F, Shape);
 
   simplifySuspendPoints(Shape);
-  buildCoroutineFrame(F, Shape, TTI, MaterializableCallback);
+  normalizeCoroutine(F, Shape, TTI);
+  buildCoroutineFrame(F, Shape, MaterializableCallback);
   replaceFrameSizeAndAlignment(Shape);
   bool isNoSuspendCoroutine = Shape.CoroSuspends.empty();
 

@llvmbot
Copy link
Member

llvmbot commented Sep 10, 2024

@llvm/pr-subscribers-coroutines

Author: Tyler Nowicki (TylerNowicki)

Changes
  • Split buildCoroutineFrame into code related to normalization and code related to actually building the coroutine frame.
  • This will enable future specialization of buildCoroutineFrame for different ABIs while the normalization can be done by splitCoroutine prior to calling buildCoroutineFrame.

See RFC for more info: https://discourse.llvm.org/t/rfc-abi-objects-for-coroutines/81057


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

3 Files Affected:

  • (modified) llvm/lib/Transforms/Coroutines/CoroFrame.cpp (+8-6)
  • (modified) llvm/lib/Transforms/Coroutines/CoroInternal.h (+3-1)
  • (modified) llvm/lib/Transforms/Coroutines/CoroSplit.cpp (+2-1)
diff --git a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
index 4b76fc79361008..8ee4bfa3b888df 100644
--- a/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroFrame.cpp
@@ -1754,7 +1754,8 @@ static bool willLeaveFunctionImmediatelyAfter(BasicBlock *BB,
   if (depth == 0) return false;
 
   // If this is a suspend block, we're about to exit the resumption function.
-  if (isSuspendBlock(BB)) return true;
+  if (isSuspendBlock(BB))
+    return true;
 
   // Recurse into the successors.
   for (auto *Succ : successors(BB)) {
@@ -2288,9 +2289,8 @@ static void doRematerializations(
   rewriteMaterializableInstructions(AllRemats);
 }
 
-void coro::buildCoroutineFrame(
-    Function &F, Shape &Shape, TargetTransformInfo &TTI,
-    const std::function<bool(Instruction &)> &MaterializableCallback) {
+void coro::normalizeCoroutine(Function &F, coro::Shape &Shape,
+                              TargetTransformInfo &TTI) {
   // Don't eliminate swifterror in async functions that won't be split.
   if (Shape.ABI != coro::ABI::Async || !Shape.CoroSuspends.empty())
     eliminateSwiftError(F, Shape);
@@ -2337,10 +2337,12 @@ void coro::buildCoroutineFrame(
   // Transforms multi-edge PHI Nodes, so that any value feeding into a PHI will
   // never have its definition separated from the PHI by the suspend point.
   rewritePHIs(F);
+}
 
-  // Build suspend crossing info.
+void coro::buildCoroutineFrame(
+    Function &F, Shape &Shape,
+    const std::function<bool(Instruction &)> &MaterializableCallback) {
   SuspendCrossingInfo Checker(F, Shape.CoroSuspends, Shape.CoroEnds);
-
   doRematerializations(F, Checker, MaterializableCallback);
 
   const DominatorTree DT(F);
diff --git a/llvm/lib/Transforms/Coroutines/CoroInternal.h b/llvm/lib/Transforms/Coroutines/CoroInternal.h
index be86f96525b677..698c21a797420a 100644
--- a/llvm/lib/Transforms/Coroutines/CoroInternal.h
+++ b/llvm/lib/Transforms/Coroutines/CoroInternal.h
@@ -281,8 +281,10 @@ struct LLVM_LIBRARY_VISIBILITY Shape {
 };
 
 bool defaultMaterializable(Instruction &V);
+void normalizeCoroutine(Function &F, coro::Shape &Shape,
+                        TargetTransformInfo &TTI);
 void buildCoroutineFrame(
-    Function &F, Shape &Shape, TargetTransformInfo &TTI,
+    Function &F, Shape &Shape,
     const std::function<bool(Instruction &)> &MaterializableCallback);
 CallInst *createMustTailCall(DebugLoc Loc, Function *MustTailCallFn,
                              TargetTransformInfo &TTI,
diff --git a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
index 494c4d632de95f..dc3829d7f28eb1 100644
--- a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
@@ -2030,7 +2030,8 @@ splitCoroutine(Function &F, SmallVectorImpl<Function *> &Clones,
   lowerAwaitSuspends(F, Shape);
 
   simplifySuspendPoints(Shape);
-  buildCoroutineFrame(F, Shape, TTI, MaterializableCallback);
+  normalizeCoroutine(F, Shape, TTI);
+  buildCoroutineFrame(F, Shape, MaterializableCallback);
   replaceFrameSizeAndAlignment(Shape);
   bool isNoSuspendCoroutine = Shape.CoroSuspends.empty();
 

Copy link
Member

@yuxuanchen1997 yuxuanchen1997 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I think a neutral/positive change. One thing I am worried was that how we are going to distinguish a "normalized" vs a "unnormalized" coroutine? i.e. How do we find out that we missed a normalization here?

@TylerNowicki
Copy link
Collaborator Author

Overall I think a neutral/positive change. One thing I am worried was that how we are going to distinguish a "normalized" vs a "unnormalized" coroutine? i.e. How do we find out that we missed a normalization here?

Normalization performs these important steps:

  1. split around each suspend, this adds BBs before/after each suspend so each suspend now exists in its own block.
  2. split around coro.end (similar to above)
  3. break critical edges and add single-edge phis in the new blocks (also removing other single-edge phis).

Each of these things can individually be tested
A) Check that each suspend is the only inst in its BB
B) Check that coro.end is the only inst in its BB
C) Check that each edge of a multi-edge phis is preceded by single-edge phi in an immediate pred

For 1) and 2) I believe the purpose of the transform is in part for suspend crossing info's analysis so it can specifically 'mark' the suspend blocks and identify the end of the coroutine. There are some existing places within suspend crossing info that visit the CoroSuspends and CoroEnds so we could check A) and B) there.

For 3) I believe the purpose of this transform is for insertSpills to work properly. Infact there is already a check for the result of this transform!

assert(PN->getNumIncomingValues() == 1 &&
           "unexpected number of incoming "
           "values in the PHINode");

I think to verify the result of normalization we just need to add checks A) and B) to suspend crossing info.

@TylerNowicki
Copy link
Collaborator Author

TylerNowicki commented Sep 10, 2024

Overall I think a neutral/positive change. One thing I am worried was that how we are going to distinguish a "normalized" vs a "unnormalized" coroutine? i.e. How do we find out that we missed a normalization here?

I posted a PR to add a couple of asserts that check for normalization #108096. The PR includes this change #108076 (I posted it too quickly), I will rebase after this change merges.

Copy link
Member

@ChuanqiXu9 ChuanqiXu9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines +1757 to +1758
if (isSuspendBlock(BB))
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: generally we don't unrelated changes.

@ChuanqiXu9
Copy link
Member

I think the concept normalization is pretty narrow in CoroFrame itself (or even buildCoroutineFrame itself). It is simply some transformations to make it easier to construct and use the frame. So I didn't take it as a formal concept.

@TylerNowicki TylerNowicki merged commit 9a9f155 into llvm:main Sep 11, 2024
@TylerNowicki TylerNowicki deleted the users/tylernowicki/coro-refactor3 branch September 11, 2024 14:38
TylerNowicki pushed a commit that referenced this pull request Sep 12, 2024
* Add asserts to verify normalization of the coroutine happened.
* This will be important when normalization becomes part of an ABI
object.

--- From a previous discussion here
#108076

Normalization performs these important steps:

split around each suspend, this adds BBs before/after each suspend so
each suspend now exists in its own block.
split around coro.end (similar to above)
break critical edges and add single-edge phis in the new blocks (also
removing other single-edge phis).
Each of these things can individually be tested
A) Check that each suspend is the only inst in its BB
B) Check that coro.end is the only inst in its BB
C) Check that each edge of a multi-edge phis is preceded by single-edge
phi in an immediate pred

For 1) and 2) I believe the purpose of the transform is in part for
suspend crossing info's analysis so it can specifically 'mark' the
suspend blocks and identify the end of the coroutine. There are some
existing places within suspend crossing info that visit the CoroSuspends
and CoroEnds so we could check A) and B) there.

For 3) I believe the purpose of this transform is for insertSpills to
work properly. Infact there is already a check for the result of this
transform!

assert(PN->getNumIncomingValues() == 1 &&
           "unexpected number of incoming "
           "values in the PHINode");

I think to verify the result of normalization we just need to add checks
A) and B) to suspend crossing info.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants