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