1a6628e59SEugene Zhulenev //===- AsyncRuntimeRefCountingOpt.cpp - Async Ref Counting --------------===//
2a6628e59SEugene Zhulenev //
3a6628e59SEugene Zhulenev // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4a6628e59SEugene Zhulenev // See https://llvm.org/LICENSE.txt for license information.
5a6628e59SEugene Zhulenev // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6a6628e59SEugene Zhulenev //
7a6628e59SEugene Zhulenev //===----------------------------------------------------------------------===//
8a6628e59SEugene Zhulenev //
9a6628e59SEugene Zhulenev // Optimize Async dialect reference counting operations.
10a6628e59SEugene Zhulenev //
11a6628e59SEugene Zhulenev //===----------------------------------------------------------------------===//
12a6628e59SEugene Zhulenev 
13a6628e59SEugene Zhulenev #include "PassDetail.h"
14a6628e59SEugene Zhulenev #include "mlir/Dialect/Async/IR/Async.h"
15a6628e59SEugene Zhulenev #include "mlir/Dialect/Async/Passes.h"
16a6628e59SEugene Zhulenev #include "llvm/ADT/SmallSet.h"
17*297a5b7cSNico Weber #include "llvm/Support/Debug.h"
18a6628e59SEugene Zhulenev 
19a6628e59SEugene Zhulenev using namespace mlir;
20a6628e59SEugene Zhulenev using namespace mlir::async;
21a6628e59SEugene Zhulenev 
22a6628e59SEugene Zhulenev #define DEBUG_TYPE "async-ref-counting"
23a6628e59SEugene Zhulenev 
24a6628e59SEugene Zhulenev namespace {
25a6628e59SEugene Zhulenev 
26a6628e59SEugene Zhulenev class AsyncRuntimeRefCountingOptPass
27a6628e59SEugene Zhulenev     : public AsyncRuntimeRefCountingOptBase<AsyncRuntimeRefCountingOptPass> {
28a6628e59SEugene Zhulenev public:
29a6628e59SEugene Zhulenev   AsyncRuntimeRefCountingOptPass() = default;
308a316b00SEugene Zhulenev   void runOnOperation() override;
31a6628e59SEugene Zhulenev 
32a6628e59SEugene Zhulenev private:
33a6628e59SEugene Zhulenev   LogicalResult optimizeReferenceCounting(
34a6628e59SEugene Zhulenev       Value value, llvm::SmallDenseMap<Operation *, Operation *> &cancellable);
35a6628e59SEugene Zhulenev };
36a6628e59SEugene Zhulenev 
37a6628e59SEugene Zhulenev } // namespace
38a6628e59SEugene Zhulenev 
39a6628e59SEugene Zhulenev LogicalResult AsyncRuntimeRefCountingOptPass::optimizeReferenceCounting(
40a6628e59SEugene Zhulenev     Value value, llvm::SmallDenseMap<Operation *, Operation *> &cancellable) {
41a6628e59SEugene Zhulenev   Region *definingRegion = value.getParentRegion();
42a6628e59SEugene Zhulenev 
43a6628e59SEugene Zhulenev   // Find all users of the `value` inside each block, including operations that
44a6628e59SEugene Zhulenev   // do not use `value` directly, but have a direct use inside nested region(s).
45a6628e59SEugene Zhulenev   //
46a6628e59SEugene Zhulenev   // Example:
47a6628e59SEugene Zhulenev   //
48a6628e59SEugene Zhulenev   //  ^bb1:
49a6628e59SEugene Zhulenev   //    %token = ...
50a6628e59SEugene Zhulenev   //    scf.if %cond {
51a6628e59SEugene Zhulenev   //      ^bb2:
52a6628e59SEugene Zhulenev   //      async.runtime.await %token : !async.token
53a6628e59SEugene Zhulenev   //    }
54a6628e59SEugene Zhulenev   //
55a6628e59SEugene Zhulenev   // %token has a use inside ^bb2 (`async.runtime.await`) and inside ^bb1
56a6628e59SEugene Zhulenev   // (`scf.if`).
57a6628e59SEugene Zhulenev 
58a6628e59SEugene Zhulenev   struct BlockUsersInfo {
59a6628e59SEugene Zhulenev     llvm::SmallVector<RuntimeAddRefOp, 4> addRefs;
60a6628e59SEugene Zhulenev     llvm::SmallVector<RuntimeDropRefOp, 4> dropRefs;
61a6628e59SEugene Zhulenev     llvm::SmallVector<Operation *, 4> users;
62a6628e59SEugene Zhulenev   };
63a6628e59SEugene Zhulenev 
64a6628e59SEugene Zhulenev   llvm::DenseMap<Block *, BlockUsersInfo> blockUsers;
65a6628e59SEugene Zhulenev 
66a6628e59SEugene Zhulenev   auto updateBlockUsersInfo = [&](Operation *user) {
67a6628e59SEugene Zhulenev     BlockUsersInfo &info = blockUsers[user->getBlock()];
68a6628e59SEugene Zhulenev     info.users.push_back(user);
69a6628e59SEugene Zhulenev 
70a6628e59SEugene Zhulenev     if (auto addRef = dyn_cast<RuntimeAddRefOp>(user))
71a6628e59SEugene Zhulenev       info.addRefs.push_back(addRef);
72a6628e59SEugene Zhulenev     if (auto dropRef = dyn_cast<RuntimeDropRefOp>(user))
73a6628e59SEugene Zhulenev       info.dropRefs.push_back(dropRef);
74a6628e59SEugene Zhulenev   };
75a6628e59SEugene Zhulenev 
76a6628e59SEugene Zhulenev   for (Operation *user : value.getUsers()) {
77a6628e59SEugene Zhulenev     while (user->getParentRegion() != definingRegion) {
78a6628e59SEugene Zhulenev       updateBlockUsersInfo(user);
79a6628e59SEugene Zhulenev       user = user->getParentOp();
80a6628e59SEugene Zhulenev       assert(user != nullptr && "value user lies outside of the value region");
81a6628e59SEugene Zhulenev     }
82a6628e59SEugene Zhulenev 
83a6628e59SEugene Zhulenev     updateBlockUsersInfo(user);
84a6628e59SEugene Zhulenev   }
85a6628e59SEugene Zhulenev 
86a6628e59SEugene Zhulenev   // Sort all operations found in the block.
87a6628e59SEugene Zhulenev   auto preprocessBlockUsersInfo = [](BlockUsersInfo &info) -> BlockUsersInfo & {
88a6628e59SEugene Zhulenev     auto isBeforeInBlock = [](Operation *a, Operation *b) -> bool {
89a6628e59SEugene Zhulenev       return a->isBeforeInBlock(b);
90a6628e59SEugene Zhulenev     };
91a6628e59SEugene Zhulenev     llvm::sort(info.addRefs, isBeforeInBlock);
92a6628e59SEugene Zhulenev     llvm::sort(info.dropRefs, isBeforeInBlock);
93a6628e59SEugene Zhulenev     llvm::sort(info.users, [&](Operation *a, Operation *b) -> bool {
94a6628e59SEugene Zhulenev       return isBeforeInBlock(a, b);
95a6628e59SEugene Zhulenev     });
96a6628e59SEugene Zhulenev 
97a6628e59SEugene Zhulenev     return info;
98a6628e59SEugene Zhulenev   };
99a6628e59SEugene Zhulenev 
100a6628e59SEugene Zhulenev   // Find and erase matching pairs of `add_ref` / `drop_ref` operations in the
101a6628e59SEugene Zhulenev   // blocks that modify the reference count of the `value`.
102a6628e59SEugene Zhulenev   for (auto &kv : blockUsers) {
103a6628e59SEugene Zhulenev     BlockUsersInfo &info = preprocessBlockUsersInfo(kv.second);
104a6628e59SEugene Zhulenev 
105a6628e59SEugene Zhulenev     for (RuntimeAddRefOp addRef : info.addRefs) {
106a6628e59SEugene Zhulenev       for (RuntimeDropRefOp dropRef : info.dropRefs) {
107a6628e59SEugene Zhulenev         // `drop_ref` operation after the `add_ref` with matching count.
108a6628e59SEugene Zhulenev         if (dropRef.count() != addRef.count() ||
109a6628e59SEugene Zhulenev             dropRef->isBeforeInBlock(addRef.getOperation()))
110a6628e59SEugene Zhulenev           continue;
111a6628e59SEugene Zhulenev 
112a6628e59SEugene Zhulenev         // Try to cancel the pair of `add_ref` and `drop_ref` operations.
113a6628e59SEugene Zhulenev         auto emplaced = cancellable.try_emplace(dropRef.getOperation(),
114a6628e59SEugene Zhulenev                                                 addRef.getOperation());
115a6628e59SEugene Zhulenev 
116a6628e59SEugene Zhulenev         if (!emplaced.second) // `drop_ref` was already marked for removal
117a6628e59SEugene Zhulenev           continue;           // go to the next `drop_ref`
118a6628e59SEugene Zhulenev 
119a6628e59SEugene Zhulenev         if (emplaced.second) // successfully cancelled `add_ref` <-> `drop_ref`
120a6628e59SEugene Zhulenev           break;             // go to the next `add_ref`
121a6628e59SEugene Zhulenev       }
122a6628e59SEugene Zhulenev     }
123a6628e59SEugene Zhulenev   }
124a6628e59SEugene Zhulenev 
125a6628e59SEugene Zhulenev   return success();
126a6628e59SEugene Zhulenev }
127a6628e59SEugene Zhulenev 
1288a316b00SEugene Zhulenev void AsyncRuntimeRefCountingOptPass::runOnOperation() {
1298a316b00SEugene Zhulenev   Operation *op = getOperation();
130a6628e59SEugene Zhulenev 
131a6628e59SEugene Zhulenev   // Mapping from `dropRef.getOperation()` to `addRef.getOperation()`.
132a6628e59SEugene Zhulenev   //
133a6628e59SEugene Zhulenev   // Find all cancellable pairs of operation and erase them in the end to keep
134a6628e59SEugene Zhulenev   // all iterators valid while we are walking the function operations.
135a6628e59SEugene Zhulenev   llvm::SmallDenseMap<Operation *, Operation *> cancellable;
136a6628e59SEugene Zhulenev 
137a6628e59SEugene Zhulenev   // Optimize reference counting for values defined by block arguments.
1388a316b00SEugene Zhulenev   WalkResult blockWalk = op->walk([&](Block *block) -> WalkResult {
139a6628e59SEugene Zhulenev     for (BlockArgument arg : block->getArguments())
140a6628e59SEugene Zhulenev       if (isRefCounted(arg.getType()))
141a6628e59SEugene Zhulenev         if (failed(optimizeReferenceCounting(arg, cancellable)))
142a6628e59SEugene Zhulenev           return WalkResult::interrupt();
143a6628e59SEugene Zhulenev 
144a6628e59SEugene Zhulenev     return WalkResult::advance();
145a6628e59SEugene Zhulenev   });
146a6628e59SEugene Zhulenev 
147a6628e59SEugene Zhulenev   if (blockWalk.wasInterrupted())
148a6628e59SEugene Zhulenev     signalPassFailure();
149a6628e59SEugene Zhulenev 
150a6628e59SEugene Zhulenev   // Optimize reference counting for values defined by operation results.
1518a316b00SEugene Zhulenev   WalkResult opWalk = op->walk([&](Operation *op) -> WalkResult {
152a6628e59SEugene Zhulenev     for (unsigned i = 0; i < op->getNumResults(); ++i)
153a6628e59SEugene Zhulenev       if (isRefCounted(op->getResultTypes()[i]))
154a6628e59SEugene Zhulenev         if (failed(optimizeReferenceCounting(op->getResult(i), cancellable)))
155a6628e59SEugene Zhulenev           return WalkResult::interrupt();
156a6628e59SEugene Zhulenev 
157a6628e59SEugene Zhulenev     return WalkResult::advance();
158a6628e59SEugene Zhulenev   });
159a6628e59SEugene Zhulenev 
160a6628e59SEugene Zhulenev   if (opWalk.wasInterrupted())
161a6628e59SEugene Zhulenev     signalPassFailure();
162a6628e59SEugene Zhulenev 
163a6628e59SEugene Zhulenev   LLVM_DEBUG({
164a6628e59SEugene Zhulenev     llvm::dbgs() << "Found " << cancellable.size()
165a6628e59SEugene Zhulenev                  << " cancellable reference counting operations\n";
166a6628e59SEugene Zhulenev   });
167a6628e59SEugene Zhulenev 
168a6628e59SEugene Zhulenev   // Erase all cancellable `add_ref <-> drop_ref` operation pairs.
169a6628e59SEugene Zhulenev   for (auto &kv : cancellable) {
170a6628e59SEugene Zhulenev     kv.first->erase();
171a6628e59SEugene Zhulenev     kv.second->erase();
172a6628e59SEugene Zhulenev   }
173a6628e59SEugene Zhulenev }
174a6628e59SEugene Zhulenev 
1758a316b00SEugene Zhulenev std::unique_ptr<Pass> mlir::createAsyncRuntimeRefCountingOptPass() {
176a6628e59SEugene Zhulenev   return std::make_unique<AsyncRuntimeRefCountingOptPass>();
177a6628e59SEugene Zhulenev }
178