CIRCT 23.0.0git
Loading...
Searching...
No Matches
AllocateState.cpp
Go to the documentation of this file.
1//===- AllocateState.cpp --------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
12#include "mlir/IR/ImplicitLocOpBuilder.h"
13#include "mlir/Pass/Pass.h"
14#include "llvm/Support/Debug.h"
15
16#define DEBUG_TYPE "arc-allocate-state"
17
18namespace circt {
19namespace arc {
20#define GEN_PASS_DEF_ALLOCATESTATE
21#include "circt/Dialect/Arc/ArcPasses.h.inc"
22} // namespace arc
23} // namespace circt
24
25using namespace mlir;
26using namespace circt;
27using namespace arc;
28
30
31//===----------------------------------------------------------------------===//
32// Pass Implementation
33//===----------------------------------------------------------------------===//
34
35namespace {
36
37struct AllocateStatePass
38 : public arc::impl::AllocateStateBase<AllocateStatePass> {
39
40 using AllocateStateBase::AllocateStateBase;
41
42 void runOnOperation() override;
43 void allocateBlock(Block *block, Value rootStorage);
44 void allocateOps(Value storage, Block *block, ArrayRef<Operation *> ops,
45 unsigned padding);
46 void tapNamedState(AllocStateOp allocOp, IntegerAttr offset);
47
48 SmallVector<Attribute> traceTaps;
49};
50} // namespace
51
52void AllocateStatePass::runOnOperation() {
53 traceTaps.clear();
54
55 ModelOp modelOp = getOperation();
56 LLVM_DEBUG(llvm::dbgs() << "Allocating state in `" << modelOp.getName()
57 << "`\n");
58
59 // Walk the blocks from innermost to outermost and group all state allocations
60 // in that block in one larger allocation.
61 Value rootStorage = modelOp.getBodyBlock().getArgument(0);
62 modelOp.walk([&](Block *block) { allocateBlock(block, rootStorage); });
63
64 if (!traceTaps.empty())
65 modelOp.setTraceTapsAttr(ArrayAttr::get(modelOp.getContext(), traceTaps));
66}
67
68/// Create a TraceTap attribute for the given `allocOp` if it carries one or
69/// more names. Annotates all StateWriteOp users of the allocated state to
70/// preserve the association between the write operations and the signal.
71void AllocateStatePass::tapNamedState(AllocStateOp allocOp,
72 IntegerAttr offset) {
73 TraceTapAttr tapAttr;
74 auto valueType = cast<StateType>(allocOp.getType()).getType();
75 auto typeAttr = TypeAttr::get(valueType);
76 // Check for "name" or "names" attribute
77 if (auto namesAttr = allocOp->getAttrOfType<ArrayAttr>("names")) {
78 if (!namesAttr.empty()) {
79 tapAttr = TraceTapAttr::get(allocOp.getContext(), typeAttr,
80 offset.getValue().getZExtValue(), namesAttr);
81 }
82 } else if (auto nameAttr = allocOp->getAttrOfType<StringAttr>("name")) {
83 auto arrayAttr = ArrayAttr::get(nameAttr.getContext(), {nameAttr});
84 tapAttr = TraceTapAttr::get(allocOp.getContext(), typeAttr,
85 offset.getValue().getZExtValue(), arrayAttr);
86 }
87 if (!tapAttr)
88 return;
89 traceTaps.push_back(tapAttr);
90 // Annotate the StateWrite users with the current ModelOp and the newly
91 // created tap's index in the array.
92 auto indexAttr = IntegerAttr::get(
93 IntegerType::get(allocOp.getContext(), 64,
94 IntegerType::SignednessSemantics::Unsigned),
95 traceTaps.size() - 1);
96 auto modelSymAttr = FlatSymbolRefAttr::get(getOperation().getSymNameAttr());
97
98 for (auto *user : allocOp.getResult().getUsers()) {
99 if (auto writeUser = dyn_cast<StateWriteOp>(user)) {
100 // TODO: Can a StateWriteOp be shared across models?
101 assert(!(writeUser.getTraceTapIndex() || writeUser.getTraceTapModel()));
102 writeUser.setTraceTapIndexAttr(indexAttr);
103 writeUser.setTraceTapModelAttr(modelSymAttr);
104 }
105 }
106}
107
108void AllocateStatePass::allocateBlock(Block *block, Value rootStorage) {
110
111 // Group operations by their storage. There is generally just one storage,
112 // passed into the model as a block argument.
113 for (auto &op : *block) {
114 if (isa<AllocStateOp, RootInputOp, RootOutputOp, AllocMemoryOp,
115 AllocStorageOp>(&op))
116 opsByStorage[op.getOperand(0)].push_back(&op);
117 }
118 LLVM_DEBUG(llvm::dbgs() << "- Visiting block in "
119 << block->getParentOp()->getName() << "\n");
120
121 // Actually allocate each operation. Root storage gets padding for the model
122 // header.
123 bool isRootBlock = block == rootStorage.getParentBlock();
124 // Ensure `allocateOps` runs on the root storage, even without allocations,
125 // to set the `storageBytes` attribute.
126 if (isRootBlock)
127 opsByStorage.insert({rootStorage, {}});
128 for (auto &[storage, ops] : opsByStorage) {
129 unsigned padding = isRootBlock && storage == rootStorage ? kStateOffset : 0;
130 allocateOps(storage, block, ops, padding);
131 }
132}
133
134void AllocateStatePass::allocateOps(Value storage, Block *block,
135 ArrayRef<Operation *> ops,
136 unsigned padding) {
137 SmallVector<std::tuple<Value, Value, IntegerAttr>> gettersToCreate;
138
139 // Helper function to allocate storage aligned to its own size, or 16 bytes
140 // at most.
141 unsigned currentByte = padding;
142 auto allocBytes = [&](unsigned numBytes) {
143 currentByte = llvm::alignToPowerOf2(
144 currentByte, llvm::bit_ceil(std::min(numBytes, 16U)));
145 unsigned offset = currentByte;
146 currentByte += numBytes;
147 return offset;
148 };
149
150 // Allocate storage for the operations.
151 OpBuilder builder(block->getParentOp());
152 for (auto *op : ops) {
153 if (isa<AllocStateOp, RootInputOp, RootOutputOp>(op)) {
154 auto result = op->getResult(0);
155 auto storage = op->getOperand(0);
156 unsigned numBytes = cast<StateType>(result.getType()).getByteWidth();
157 auto offset = builder.getI32IntegerAttr(allocBytes(numBytes));
158 op->setAttr("offset", offset);
159 gettersToCreate.emplace_back(result, storage, offset);
160 if (insertTraceTaps)
161 if (auto allocStateOp = dyn_cast<AllocStateOp>(op))
162 tapNamedState(allocStateOp, offset);
163 continue;
164 }
165
166 if (auto memOp = dyn_cast<AllocMemoryOp>(op)) {
167 auto memType = memOp.getType();
168 unsigned stride = memType.getStride();
169 unsigned numBytes = memType.getNumWords() * stride;
170 auto offset = builder.getI32IntegerAttr(allocBytes(numBytes));
171 op->setAttr("offset", offset);
172 op->setAttr("stride", builder.getI32IntegerAttr(stride));
173 gettersToCreate.emplace_back(memOp, memOp.getStorage(), offset);
174 continue;
175 }
176
177 if (auto allocStorageOp = dyn_cast<AllocStorageOp>(op)) {
178 auto offset =
179 builder.getI32IntegerAttr(allocBytes(allocStorageOp.getSize()));
180 allocStorageOp.setOffsetAttr(offset);
181 gettersToCreate.emplace_back(allocStorageOp, allocStorageOp.getInput(),
182 offset);
183 continue;
184 }
185
186 assert("unsupported op for allocation" && false);
187 }
188
189 // For every user of the alloc op, create a local `StorageGetOp`.
190 // First, create an ordering of operations to avoid a very expensive
191 // combination of isBeforeInBlock and moveBefore calls (which can be O(n²))
192 DenseMap<Operation *, unsigned> opOrder;
193 block->walk([&](Operation *op) { opOrder.insert({op, opOrder.size()}); });
194 SmallVector<StorageGetOp> getters;
195 for (auto [result, storage, offset] : gettersToCreate) {
197 for (auto *user : llvm::make_early_inc_range(result.getUsers())) {
198 auto &getter = getterForBlock[user->getBlock()];
199 // Create a local getter in front of each user, except for
200 // `AllocStorageOp`s, for which we create a block-wider accessor.
201 auto userOrder = opOrder.lookup(user);
202 if (!getter || !result.getDefiningOp<AllocStorageOp>()) {
203 ImplicitLocOpBuilder builder(result.getLoc(), user);
204 getter =
205 StorageGetOp::create(builder, result.getType(), storage, offset);
206 getters.push_back(getter);
207 opOrder[getter] = userOrder;
208 } else if (userOrder < opOrder.lookup(getter)) {
209 getter->moveBefore(user);
210 opOrder[getter] = userOrder;
211 }
212 user->replaceUsesOfWith(result, getter);
213 }
214 }
215
216 // Create the substorage accessor at the beginning of the block, or update the
217 // root storage type to reflect the total allocation size. Root storage is
218 // indicated by non-zero padding.
219 if (padding != 0) {
220 getOperation().setStorageBytes(currentByte);
221 } else {
222 auto substorage = AllocStorageOp::create(
223 builder, block->getParentOp()->getLoc(),
224 StorageType::get(&getContext()), storage, currentByte, {});
225 for (auto *op : ops)
226 op->replaceUsesOfWith(storage, substorage);
227 for (auto op : getters)
228 op->replaceUsesOfWith(storage, substorage);
229 }
230}
assert(baseType &&"element must be base type")
Definition arc.py:1
constexpr unsigned kStateOffset
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.