CIRCT 24.0.0git
Loading...
Searching...
No Matches
ElaborateObject.cpp
Go to the documentation of this file.
1//===- ElaborateObject.cpp - OM compile-time evaluation pass --------------===//
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//
9// This pass performs evaluation of OM classes by inlining object
10// instantiations and folding field accesses. It replaces the runtime Evaluator
11// framework with static compile-time evaluation.
12//
13//===----------------------------------------------------------------------===//
14
18#include "circt/Support/LLVM.h"
19#include "mlir/IR/Builders.h"
20#include "mlir/IR/Diagnostics.h"
21#include "mlir/IR/IRMapping.h"
22#include "mlir/IR/Matchers.h"
23#include "mlir/IR/Operation.h"
24#include "mlir/IR/SymbolTable.h"
25#include "mlir/Interfaces/SideEffectInterfaces.h"
26#include "mlir/Support/WalkResult.h"
27#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
28#include "llvm/ADT/STLExtras.h"
29#include "llvm/Support/LogicalResult.h"
30
31namespace circt {
32namespace om {
33#define GEN_PASS_DEF_ELABORATEOBJECT
34#include "circt/Dialect/OM/OMPasses.h.inc"
35} // namespace om
36} // namespace circt
37
38using namespace mlir;
39using namespace circt;
40using namespace om;
41
42namespace {
43// A map from (class name, field name) to field index.
44using FieldIndex = DenseMap<std::pair<StringAttr, StringAttr>, unsigned>;
45
46/// Pattern to inline ObjectOp instances by cloning the class body and
47/// replacing them with ElaboratedObjectOp.
48struct ObjectOpInliningPattern : public OpRewritePattern<ObjectOp> {
49 ObjectOpInliningPattern(MLIRContext *context, SymbolTable &symTable,
50 bool replaceExternalWithUnknown)
51 : OpRewritePattern<ObjectOp>(context), symTable(symTable),
52 replaceExternalWithUnknown(replaceExternalWithUnknown) {}
53
54 LogicalResult matchAndRewrite(ObjectOp objOp,
55 PatternRewriter &rewriter) const override {
56 auto classLike =
57 symTable.lookup<ClassLike>(objOp.getClassNameAttr().getAttr());
58 assert(classLike);
59
60 // External classes cannot be elaborated; replace with unknown values.
61 if (isa<ClassExternOp>(classLike)) {
62 if (!replaceExternalWithUnknown)
63 return failure();
64 rewriter.replaceOpWithNewOp<UnknownValueOp>(objOp, objOp.getType());
65 return success();
66 }
67
68 auto classOp = dyn_cast<ClassOp>(classLike.getOperation());
69 if (!classOp)
70 return failure();
71
72 IRMapping mapper;
73 for (auto [formal, actual] : llvm::zip(
74 classOp.getBodyBlock()->getArguments(), objOp.getActualParams()))
75 mapper.map(formal, actual);
76
77 // Clone the class body into a temporary region with argument substitution.
78 Region clonedRegion;
79 classOp.getBody().cloneInto(&clonedRegion, mapper);
80 Block *clonedBlock = &clonedRegion.front();
81
82 auto clonedFields = cast<ClassFieldsOp>(clonedBlock->getTerminator());
83 SmallVector<Value> fieldValues(clonedFields.getFields());
84 // Propagate the class's per-field locations onto each field value, fused
85 // with the value's existing location.
86 if (auto classOp = dyn_cast<ClassOp>(classLike.getOperation()))
87 for (auto [i, v] : llvm::enumerate(fieldValues)) {
88 Location fieldLoc =
89 rewriter.getFusedLoc({classOp.getFieldLocByIndex(i), v.getLoc()});
90 if (auto *fieldOp = v.getDefiningOp()) {
91 rewriter.modifyOpInPlace(fieldOp, [&] { fieldOp->setLoc(fieldLoc); });
92 } else {
93 auto &val = v;
94 rewriter.modifyOpInPlace(
95 cast<BlockArgument>(v).getOwner()->getParentOp(),
96 [&] { val.setLoc(fieldLoc); });
97 }
98 }
99
100 // Erase the terminator and inline the body at the object instantiation.
101 rewriter.eraseOp(clonedFields);
102 rewriter.inlineBlockBefore(clonedBlock, objOp);
103
104 rewriter.replaceOpWithNewOp<ElaboratedObjectOp>(objOp, classLike,
105 fieldValues);
106
107 return success();
108 }
109
110 const SymbolTable &symTable;
111 bool replaceExternalWithUnknown;
112};
113
114/// Pattern to fold ObjectFieldOp on ElaboratedObjectOp by directly accessing
115/// the field value operands.
116struct EvaluateObjectField : OpRewritePattern<ObjectFieldOp> {
117 EvaluateObjectField(MLIRContext *context, const SymbolTable &symTable,
118 const FieldIndex &fieldIndexes)
119 : OpRewritePattern<ObjectFieldOp>(context), symTable(symTable),
120 fieldIndexes(fieldIndexes) {}
121
122 LogicalResult matchAndRewrite(ObjectFieldOp op,
123 PatternRewriter &rewriter) const override {
124 // Only fold if the object is an ElaboratedObjectOp.
125 auto elaboratedOp = op.getObject().getDefiningOp<ElaboratedObjectOp>();
126 if (!elaboratedOp)
127 return failure();
128
129 auto classLike =
130 symTable.lookup<ClassLike>(elaboratedOp.getClassNameAttr().getAttr());
131 assert(classLike);
132
133 // Find the field index and get the corresponding value.
134 auto index =
135 fieldIndexes.at({classLike.getSymNameAttr(), op.getFieldAttr()});
136 auto result = elaboratedOp.getFieldValues()[index];
137
138 // Skip cycles where a field references itself.
139 // This will be raised as an error later.
140 if (op.getResult() == result)
141 return failure();
142
143 rewriter.replaceOp(op, result);
144 return success();
145 }
146
147 const SymbolTable &symTable;
148 const FieldIndex &fieldIndexes;
149};
150
151/// Pattern to propagate UnknownValueOp through pure OM operations.
152/// If any operand is unknown, all results become unknown.
153struct UnknownPropagationPattern : RewritePattern {
154 UnknownPropagationPattern(MLIRContext *context)
155 : RewritePattern(MatchAnyOpTypeTag(), /*benefit=*/1, context) {}
156
157 LogicalResult matchAndRewrite(Operation *op,
158 PatternRewriter &rewriter) const override {
159 // Only target pure OM operations.
160 // TODO: Consider add a trait for this if we want to have more explict
161 // behavior.
162 if (!isa_and_nonnull<OMDialect>(op->getDialect()) || !isPure(op) ||
163 op->getNumResults() == 0)
164 return failure();
165
166 // Check if any operand is an UnknownValueOp.
167 // TODO: This directly ports the existing Evaluator semantics, but it
168 // causes inconsistent evaluation for operations that can reason about
169 // known values, e.g., "and(0, unknown) -> 0".
170 if (!llvm::any_of(op->getOperands(), [](Value operand) {
171 return operand.getDefiningOp<UnknownValueOp>();
172 }))
173 return failure();
174
175 // Replace all results with UnknownValueOp.
176 SmallVector<Value> unknowns;
177 for (Type resultType : op->getResultTypes())
178 unknowns.push_back(
179 UnknownValueOp::create(rewriter, op->getLoc(), resultType));
180
181 rewriter.replaceOp(op, unknowns);
182 return success();
183 }
184};
185
186// Check if an operation can be evaluated at compile time and is valid to
187// remain in the IR after elaboration.
188bool isFullyEvaluated(Operation *op) {
189 return isa<
190 // Structure.
191 ClassOp, ClassFieldsOp, ElaboratedObjectOp, AnyCastOp,
192 // Constant-like.
193 ConstantOp, UnknownValueOp,
194 // Path.
195 FrozenBasePathCreateOp, FrozenPathCreateOp, FrozenEmptyPathOp,
196 // List.
197 ListCreateOp, ListConcatOp>(op);
198}
199
200LogicalResult verifyResult(ClassOp module, bool allowUnevaluated) {
201 auto isLegal = [allowUnevaluated](Operation *op) -> LogicalResult {
202 // Check assert satisfied.
203 if (auto assertOp = dyn_cast<PropertyAssertOp>(op)) {
204 // Check if the condition is a constant false, which means the assertion
205 // is violated.
206 auto *defOp = assertOp.getCondition().getDefiningOp();
207 APInt value;
208 auto checkAssert = [&](bool cond) -> LogicalResult {
209 if (cond) {
210 // Erase when success, serialization doesn't need to care about
211 // this.
212 op->erase();
213 return success();
214 }
215
216 // The message is supposed to be fully evaluated at this point, though
217 // it could be unknown.
218 auto messageOp =
219 dyn_cast_or_null<ConstantOp>(assertOp.getMessage().getDefiningOp());
220 if (!messageOp) {
221 if (allowUnevaluated)
222 return op->emitError("OM property assertion failed: <unevaluated>");
223
224 auto diag = emitError(op->getLoc(),
225 "OM property assertion failed, but no message "
226 "is available as the message is unevaluated");
227 diag.attachNote(assertOp.getMessage().getLoc())
228 << "unevaluated message operation is here";
229 return failure();
230 }
231
232 StringAttr message;
233 if (!matchPattern(assertOp.getMessage(), m_Constant(&message)))
234 return op->emitError()
235 << "OM property assertion failed, but no message is available "
236 "because the message is not a constant string";
237 return op->emitError("OM property assertion failed: ")
238 << message.getValue();
239 };
240
241 // Condition is a constant integer/bool - check if it's true.
242 if (matchPattern(assertOp.getCondition(), m_ConstantInt(&value)))
243 return checkAssert(!value.isZero());
244
245 // Condition is unknown - treat as passing.
246 if (auto unknownOp = dyn_cast_or_null<UnknownValueOp>(defOp))
247 return checkAssert(true);
248
249 // This means the condition was not fully evaluated.
250 if (allowUnevaluated)
251 return success();
252 return emitError(op->getLoc(), "failed to evaluate assertion condition");
253 }
254
255 if (!isFullyEvaluated(op)) {
256 if (allowUnevaluated)
257 return success();
258 return emitError(op->getLoc()) << "failed to evaluate " << op->getName();
259 }
260
261 return success();
262 };
263 bool encounteredError = false;
264 module.walk([&](Operation *op) { encounteredError |= failed(isLegal(op)); });
265
266 return failure(encounteredError);
267}
268
269struct ElaborateObjectPass
270 : public circt::om::impl::ElaborateObjectBase<ElaborateObjectPass> {
271 using Base::Base;
272
273 static LogicalResult elaborateClass(ClassOp classOp, SymbolTable &symTable,
274 FieldIndex &fieldIndexes,
275 bool allowUnevaluated = false) {
276 // Elaborate objects by inlining all ObjectOps and folding field accesses
277 // using a greedy pattern rewriter. NOTE: The conversion framework is not
278 // suitable here because inlining patterns need to be applied recursively to
279 // fully evaluate nested object instantiations.
280 RewritePatternSet patterns(classOp.getContext());
281 patterns.add<ObjectOpInliningPattern>(classOp.getContext(), symTable,
282 !allowUnevaluated);
283 patterns.add<EvaluateObjectField>(classOp.getContext(), symTable,
284 fieldIndexes);
285 patterns.add<UnknownPropagationPattern>(classOp.getContext());
286 GreedyRewriteConfig config;
287 // Disable iteration limit to allow full recursive inlining.
288 config.setMaxIterations(GreedyRewriteConfig::kNoLimit);
289 if (failed(applyPatternsGreedily(classOp, std::move(patterns), config)))
290 return failure();
291
292 // Check if elaboration succeeded after saturation.
293 return verifyResult(classOp, allowUnevaluated);
294 }
295
296 LogicalResult initialize(MLIRContext *context) override {
297 unsigned numModes =
298 allPublicClasses.getValue() + !targetClass.getValue().empty();
299 if (numModes != 1)
300 return emitError(UnknownLoc::get(context))
301 << "exactly one of 'target-class' or 'all-public-classes' must "
302 "be specified";
303 return success();
304 }
305
306 void runOnOperation() override {
307 auto module = getOperation();
308 auto &symTable = getAnalysis<SymbolTable>();
309
310 // Build a map from (class name, field name) to field index for all
311 // classes.
312 FieldIndex fieldIndexes;
313 for (auto classOp : module.getOps<ClassLike>()) {
314 auto name = classOp.getSymNameAttr();
315 for (auto [idx, fieldName] :
316 llvm::enumerate(classOp.getFieldNames().getAsRange<StringAttr>()))
317 fieldIndexes[{name, fieldName}] = idx;
318 }
319
320 // Elaborate all public classes.
321 if (allPublicClasses) {
322 for (auto classOp : module.getOps<ClassOp>()) {
323 if (!classOp.isPublic())
324 continue;
325 if (failed(elaborateClass(classOp, symTable, fieldIndexes,
326 allowUnevaluated)))
327 return signalPassFailure();
328 }
329 return;
330 }
331
332 // Normal mode: elaborate the specified target class.
333 auto classOp = symTable.lookup<ClassOp>(targetClass);
334 if (!classOp) {
335 emitError(module.getLoc())
336 << "target class '" << targetClass << "' was not found";
337 return signalPassFailure();
338 }
339
340 if (failed(
341 elaborateClass(classOp, symTable, fieldIndexes, allowUnevaluated)))
342 return signalPassFailure();
343 }
344};
345
346} // namespace
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:222
static Block * getBodyBlock(FModuleLike mod)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition om.py:1