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