CIRCT 23.0.0git
Loading...
Searching...
No Matches
Evaluator.cpp
Go to the documentation of this file.
1//===- Evaluator.cpp - Object Model dialect evaluator ---------------------===//
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 file implements the Object Model dialect Evaluator.
10//
11//===----------------------------------------------------------------------===//
12
15#include "mlir/IR/Builders.h"
16#include "mlir/IR/BuiltinAttributeInterfaces.h"
17#include "mlir/IR/Location.h"
18#include "mlir/IR/SymbolTable.h"
19#include "mlir/IR/Verifier.h"
20#include "mlir/Pass/PassManager.h"
21#include "llvm/ADT/STLExtras.h"
22#include "llvm/ADT/ScopeExit.h"
23#include "llvm/ADT/TypeSwitch.h"
24#include "llvm/ADT/iterator_range.h"
25#include "llvm/Support/Debug.h"
26
27#define DEBUG_TYPE "om-evaluator"
28
29using namespace mlir;
30using namespace circt::om;
31
32namespace {
33
34constexpr StringLiteral skipElaborationTransformAttr =
35 "om.skip_elaboration_transform";
36
37LogicalResult verifyActualParameters(ClassLike classLike,
38 ArrayRef<EvaluatorValuePtr> actualParams) {
39 auto formalParamNames =
40 classLike.getFormalParamNames().getAsRange<StringAttr>();
41 auto formalParamTypes = classLike.getBodyBlock()->getArgumentTypes();
42
43 if (actualParams.size() != formalParamTypes.size()) {
44 auto error = classLike.emitError("actual parameter list length (")
45 << actualParams.size() << ") does not match formal "
46 << "parameter list length (" << formalParamTypes.size() << ")";
47 auto &diag = error.attachNote() << "actual parameters: ";
48 bool isFirst = true;
49 for (const auto &param : actualParams) {
50 if (isFirst)
51 isFirst = false;
52 else
53 diag << ", ";
54 diag << param;
55 }
56 error.attachNote(classLike.getLoc())
57 << "formal parameters: " << formalParamTypes;
58 return failure();
59 }
60
61 for (auto [actualParam, formalParamName, formalParamType] :
62 llvm::zip(actualParams, formalParamNames, formalParamTypes)) {
63 if (!actualParam || !actualParam.get())
64 return classLike.emitError("actual parameter for ")
65 << formalParamName << " is null";
66
67 // Subtyping: if formal param is any type, any actual param may be passed.
68 if (isa<AnyType>(formalParamType))
69 continue;
70
71 Type actualParamType = actualParam->getType();
72 assert(actualParamType && "actualParamType must be non-null!");
73
74 if (actualParamType != formalParamType) {
75 auto error = classLike.emitError("actual parameter for ")
76 << formalParamName << " has invalid type";
77 error.attachNote() << "actual parameter: " << *actualParam;
78 error.attachNote() << "format parameter type: " << formalParamType;
79 return failure();
80 }
81 }
82 return success();
83}
84
85/// A helper class that builds the scratch IR for evaluating an object. This is
86/// used to convert from the evaluator's API (which uses opaque pointers to
87/// evaluator values) into actual MLIR IR.
88class ScratchIRBuilder {
89public:
90 struct InstantiationInfo {
91 StringAttr className;
92 SmallVector<EvaluatorValuePtr> actualParams;
93 };
94
95 ScratchIRBuilder(ModuleOp module, SymbolTable &symbolTable,
96 ClassLike rootClass)
97 : module(module), symbolTable(symbolTable), rootClass(rootClass),
98 wrapperClass(createWrapperClass(rootClass)) {}
99
100 FailureOr<InstantiationInfo> run(ArrayRef<EvaluatorValuePtr> actualParams);
101
102private:
103 /// Create the temporary class that owns all scratch IR.
104 ClassOp createWrapperClass(ClassLike rootClass);
105
106 /// Convert an API input value into scratch IR, preserving opaque any-typed
107 /// inputs and rejecting runtime references/cycles.
108 FailureOr<Value> materializeInput(const EvaluatorValuePtr &value,
109 Location loc, Type expectedType);
110 /// Convert a fully evaluated list value into scratch IR.
111 FailureOr<Value> materializeListInput(evaluator::ListValue *listValue,
112 Location loc);
113 /// Convert a fully evaluated object value into scratch IR.
114 FailureOr<Value> materializeObjectInput(evaluator::ObjectValue *objectValue,
115 Location loc);
116 /// Add a wrapper class parameter for an input that must stay opaque.
117 FailureOr<Value> createWrapperArgument(EvaluatorValuePtr value, Location loc,
118 Type argType);
119
120 ModuleOp module;
121 SymbolTable &symbolTable;
122 ClassLike rootClass;
123 ClassOp wrapperClass;
124 // A mapping from evaluator input values to their corresponding imported IR
125 // values.
126 DenseMap<evaluator::EvaluatorValue *, Value> importedValues;
127
128 // A set of object values that have been imported into the scratch IR, used to
129 // detect mutual references in the inputs.
130 SmallPtrSet<evaluator::ObjectValue *, 8> activeObjectImports;
131
132 SmallVector<Attribute> wrapperArgNames;
133 SmallVector<EvaluatorValuePtr> wrapperActualParams;
134};
135
136FailureOr<ScratchIRBuilder::InstantiationInfo>
137ScratchIRBuilder::run(ArrayRef<EvaluatorValuePtr> actualParams) {
138 auto *ctx = module.getContext();
139 assert(rootClass && "root class must be resolved before building scratch IR");
140 auto rootLoc = rootClass.getLoc();
141 auto rootClassName = rootClass.getSymNameAttr();
142
143 OpBuilder builder(wrapperClass.getFieldsOp());
144 builder.setInsertionPoint(wrapperClass.getFieldsOp());
145 SmallVector<Value> importedActualValues;
146 importedActualValues.reserve(actualParams.size());
147 auto formalTypes = rootClass.getBodyBlock()->getArgumentTypes();
148 for (auto [actual, expectedType] : llvm::zip(actualParams, formalTypes)) {
149 auto imported = materializeInput(actual, rootLoc, expectedType);
150 if (failed(imported))
151 return failure();
152 importedActualValues.push_back(*imported);
153 }
154
155 // Update wrapper class after materializing actual parameters.
156 wrapperClass->setAttr(wrapperClass.getFormalParamNamesAttrName(),
157 builder.getArrayAttr(wrapperArgNames));
158
159 wrapperClass.updateFields(
160 {rootLoc},
161 {ObjectOp::create(
162 builder, rootLoc,
163 ClassType::get(ctx, FlatSymbolRefAttr::get(rootClassName)),
164 rootClassName, importedActualValues)
165 .getResult()},
166 {builder.getStringAttr("root")});
167
168 if (failed(verify(module)))
169 return failure();
170
171 PassManager pm(ctx);
172 ElaborateObjectOptions options;
173 auto wrapperName = wrapperClass.getSymNameAttr();
174 options.targetClass = wrapperName.getValue().str();
175 pm.addPass(createElaborateObject(std::move(options)));
176 if (failed(pm.run(module)))
177 return failure();
178
179 return InstantiationInfo{wrapperName, std::move(wrapperActualParams)};
180}
181
182ClassOp ScratchIRBuilder::createWrapperClass(ClassLike rootClass) {
183 OpBuilder builder(module.getBody(), module.getBody()->end());
184 builder.setInsertionPointToEnd(module.getBody());
185
186 auto wrapper = ClassOp::create(builder, rootClass.getLoc(),
187 Twine("__om_evaluator_wrapper_") +
188 rootClass.getSymName());
189 (void)symbolTable.insert(wrapper);
190 Block *body = &wrapper.getBody().emplaceBlock();
191 builder.setInsertionPointToEnd(body);
192 ClassFieldsOp::create(builder, rootClass.getLoc(), ValueRange(), ArrayAttr{});
193 return wrapper;
194}
195
196FailureOr<Value>
197ScratchIRBuilder::materializeInput(const EvaluatorValuePtr &value, Location loc,
198 Type expectedType) {
199 if (!value)
200 return emitError(loc, "cannot materialize null OM evaluator value");
201
202 loc = value->getLoc();
203 if (isa<evaluator::ReferenceValue>(value.get()))
204 return emitError(loc, "cannot import OM reference value");
205 if (!expectedType)
206 return emitError(loc, "cannot import OM evaluator value without an "
207 "expected type");
208
209 // Keep any-typed values opaque at the wrapper boundary.
210 if (isa<AnyType>(expectedType))
211 return createWrapperArgument(value, loc, expectedType);
212
213 if (auto it = importedValues.find(value.get()); it != importedValues.end())
214 return it->second;
215
216 if (value->isUnknown()) {
217 OpBuilder builder(wrapperClass.getFieldsOp());
218 auto result = UnknownValueOp::create(builder, loc, expectedType);
219 importedValues[value.get()] = result.getResult();
220 return result.getResult();
221 }
222
223 return llvm::TypeSwitch<evaluator::EvaluatorValue *, FailureOr<Value>>(
224 value.get())
225 .Case([&](evaluator::AttributeValue *attrValue) -> FailureOr<Value> {
226 auto attr = attrValue->getAttr();
227 if (!attr)
228 return emitError(loc, "cannot import OM attribute value without an "
229 "attribute");
230
231 OpBuilder builder(wrapperClass.getFieldsOp());
232 auto result = ConstantOp::create(builder, loc, cast<TypedAttr>(attr));
233 importedValues[value.get()] = result.getResult();
234 return result.getResult();
235 })
236 .Case([&](evaluator::ListValue *listValue) {
237 return materializeListInput(listValue, loc);
238 })
239 .Case([&](evaluator::ObjectValue *objectValue) {
240 return materializeObjectInput(objectValue, loc);
241 })
242 .Default([&](evaluator::EvaluatorValue *) -> FailureOr<Value> {
243 auto result = createWrapperArgument(value, loc, expectedType);
244 if (succeeded(result))
245 importedValues[value.get()] = *result;
246 return result;
247 });
248}
249
250FailureOr<Value>
251ScratchIRBuilder::materializeListInput(evaluator::ListValue *listValue,
252 Location loc) {
253 if (!listValue->isFullyEvaluated())
254 return emitError(loc, "cannot import partially evaluated OM list value");
255
256 auto listType = listValue->getListType();
257 SmallVector<Value> elementValues;
258 elementValues.reserve(listValue->getElements().size());
259 for (const auto &elementValue : listValue->getElements()) {
260 auto materializedElement =
261 materializeInput(elementValue, loc, listType.getElementType());
262 if (failed(materializedElement))
263 return failure();
264 elementValues.push_back(*materializedElement);
265 }
266
267 OpBuilder builder(wrapperClass.getFieldsOp());
268 auto result = ListCreateOp::create(builder, loc, listType, elementValues);
269 importedValues[listValue] = result.getResult();
270 return result.getResult();
271}
272
273FailureOr<Value>
274ScratchIRBuilder::materializeObjectInput(evaluator::ObjectValue *objectValue,
275 Location loc) {
276 // TODO: Currently we only support importing object values that don't have
277 // mutual references with other object values in the inputs for the
278 // simplicity. We could construct mutually referencing object values with a
279 // backedge builder but currently we don't have a use case for that.
280 if (!activeObjectImports.insert(objectValue).second)
281 return emitError(loc, "cannot import mutually referential OM objects");
282
283 llvm::scope_exit popActiveObjectImport(
284 [&] { activeObjectImports.erase(objectValue); });
285
286 auto classLike = objectValue->getClassOp();
287 SmallVector<Value> fieldValues;
288 auto fieldNames = classLike.getFieldNames();
289 fieldValues.reserve(fieldNames.size());
290 for (auto fieldName : fieldNames) {
291 auto fieldNameAttr = cast<StringAttr>(fieldName);
292 auto field = objectValue->getField(fieldNameAttr);
293 if (failed(field))
294 return failure();
295 auto materializedField = materializeInput(
296 field.value(), loc, classLike.getFieldType(fieldNameAttr).value());
297 if (failed(materializedField))
298 return failure();
299 fieldValues.push_back(*materializedField);
300 }
301
302 OpBuilder builder(wrapperClass.getFieldsOp());
303 auto result =
304 ElaboratedObjectOp::create(builder, loc, classLike, fieldValues);
305 importedValues[objectValue] = result.getResult();
306 return result.getResult();
307}
308
309FailureOr<Value>
310ScratchIRBuilder::createWrapperArgument(EvaluatorValuePtr value, Location loc,
311 Type argType) {
312 Builder builder(module.getContext());
313 wrapperArgNames.push_back(
314 builder.getStringAttr(Twine("arg") + Twine(wrapperArgNames.size())));
315 wrapperActualParams.push_back(value);
316 return wrapperClass.getBodyBlock()->addArgument(argType, loc);
317}
318
319} // namespace
320
321/// Construct an Evaluator with an IR module.
322circt::om::Evaluator::Evaluator(ModuleOp mod) : symbolTable(mod) {}
323
324/// Get the Module this Evaluator is built from.
326 return cast<ModuleOp>(symbolTable.getOp());
327}
328
329SmallVector<evaluator::EvaluatorValuePtr>
331 ArrayRef<Attribute> attributes) {
332 SmallVector<evaluator::EvaluatorValuePtr> values;
333 values.reserve(attributes.size());
334 for (auto attr : attributes)
335 values.push_back(evaluator::AttributeValue::get(cast<TypedAttr>(attr)));
336 return values;
337}
338
340 using namespace evaluator;
341 // Early return if already finalized.
342 if (finalized)
343 return success();
344 // Enable the flag to avoid infinite recursions.
345 finalized = true;
346 assert(isFullyEvaluated());
347 return llvm::TypeSwitch<EvaluatorValue *, LogicalResult>(this)
349 BasePathValue, PathValue>([](auto v) { return v->finalizeImpl(); });
350}
351
353 return llvm::TypeSwitch<const EvaluatorValue *, Type>(this)
354 .Case<AttributeValue>([](auto *attr) -> Type { return attr->getType(); })
355 .Case<ObjectValue>([](auto *object) { return object->getObjectType(); })
356 .Case<ListValue>([](auto *list) { return list->getListType(); })
357 .Case<ReferenceValue>([](auto *ref) { return ref->getValueType(); })
358 .Case<BasePathValue>(
359 [this](auto *tuple) { return FrozenBasePathType::get(ctx); })
360 .Case<PathValue>(
361 [this](auto *tuple) { return FrozenPathType::get(ctx); });
362}
363
364FailureOr<evaluator::EvaluatorValuePtr>
366 using namespace circt::om::evaluator;
367
368 auto result =
369 TypeSwitch<mlir::Type, FailureOr<evaluator::EvaluatorValuePtr>>(type)
370 .Case([&](circt::om::ListType type) {
372 std::make_shared<evaluator::ListValue>(type, loc);
373 return success(result);
374 })
375 .Case([&](circt::om::ClassType type)
376 -> FailureOr<evaluator::EvaluatorValuePtr> {
377 auto classDef =
378 symbolTable.lookup<ClassLike>(type.getClassName().getValue());
379 if (!classDef)
380 return symbolTable.getOp()->emitError("unknown class name ")
381 << type.getClassName();
382
383 // Create an ObjectValue for both ClassOp and ClassExternOp
385 std::make_shared<evaluator::ObjectValue>(classDef, loc);
386
387 return success(result);
388 })
389 .Case([&](circt::om::StringType type) {
392 return success(result);
393 })
394 .Default([&](auto type) { return failure(); });
395
396 if (succeeded(result))
397 attachCounter(result.value());
398
399 return result;
400}
401
402FailureOr<evaluator::EvaluatorValuePtr> circt::om::Evaluator::getOrCreateValue(
403 Value value, ActualParameters actualParams, Location loc) {
404 LLVM_DEBUG(dbgs() << "- get: " << value << "\n");
405
406 auto it = objects.find({value, actualParams});
407 if (it != objects.end()) {
408 auto evalVal = it->second;
409 evalVal->setLocIfUnknown(loc);
410 return evalVal;
411 }
412
413 FailureOr<evaluator::EvaluatorValuePtr> result =
414 TypeSwitch<Value, FailureOr<evaluator::EvaluatorValuePtr>>(value)
415 .Case([&](BlockArgument arg) {
416 auto val = (*actualParams)[arg.getArgNumber()];
417 val->setLoc(loc);
418 return val;
419 })
420 .Case([&](OpResult result) {
421 return TypeSwitch<Operation *,
422 FailureOr<evaluator::EvaluatorValuePtr>>(
423 result.getDefiningOp())
424 .Case([&](ConstantOp op) {
425 return evaluateConstant(op, actualParams, loc);
426 })
427 .Case([&](IntegerBinaryOp op) {
428 // Create a partially evaluated AttributeValue in case we need
429 // to delay evaluation.
431 evaluator::AttributeValue::get(op.getResult().getType(),
432 loc);
433 return success(result);
434 })
435 .Case<ObjectFieldOp>([&](auto op) {
436 // Create a reference value since the value pointed by object
437 // field op is not created yet.
439 std::make_shared<evaluator::ReferenceValue>(
440 value.getType(), loc);
441 return success(result);
442 })
443 .Case<AnyCastOp>([&](AnyCastOp op) {
444 return getOrCreateValue(op.getInput(), actualParams, loc);
445 })
446 .Case<FrozenBasePathCreateOp>([&](FrozenBasePathCreateOp op) {
448 std::make_shared<evaluator::BasePathValue>(
449 op.getPathAttr(), loc);
450 return success(result);
451 })
452 .Case<FrozenPathCreateOp>([&](FrozenPathCreateOp op) {
454 std::make_shared<evaluator::PathValue>(
455 op.getTargetKindAttr(), op.getPathAttr(),
456 op.getModuleAttr(), op.getRefAttr(),
457 op.getFieldAttr(), loc);
458 return success(result);
459 })
460 .Case<FrozenEmptyPathOp>([&](FrozenEmptyPathOp op) {
462 std::make_shared<evaluator::PathValue>(
464 return success(result);
465 })
466 .Case([&](BinaryEqualityOp op) {
468 evaluator::AttributeValue::get(op.getResult().getType(),
469 loc);
470 return success(result);
471 })
472 .Case<ListCreateOp, ListConcatOp, StringConcatOp,
473 ObjectFieldOp>([&](auto op) {
474 return getPartiallyEvaluatedValue(op.getType(), loc);
475 })
476 .Case<ObjectOp>([&](auto op) {
477 return getPartiallyEvaluatedValue(op.getType(), op.getLoc());
478 })
479 .Case<ElaboratedObjectOp>([&](auto op) {
480 return getPartiallyEvaluatedValue(op.getType(), op.getLoc());
481 })
482 .Case<UnknownValueOp>(
483 [&](auto op) { return evaluateUnknownValue(op, loc); })
484 .Default([&](Operation *op) {
485 auto error = op->emitError("unable to evaluate value");
486 error.attachNote() << "value: " << value;
487 return error;
488 });
489 });
490 if (failed(result))
491 return result;
492
493 // Attach listener to newly created values
494 attachCounter(result.value());
495 objects[{value, actualParams}] = result.value();
496 return result;
497}
498
499FailureOr<evaluator::EvaluatorValuePtr>
501 ActualParameters actualParams,
502 Location loc,
503 ObjectKey instanceKey) {
504#ifndef NDEBUG
505 DebugNesting nestOne(debugNesting);
506#endif
507 LLVM_DEBUG(dbgs() << "object:\n");
508#ifndef NDEBUG
509 DebugNesting nestTwo(debugNesting);
510#endif
511 LLVM_DEBUG(dbgs() << "name: " << className << "\n");
512
513 auto classDef = symbolTable.lookup<ClassLike>(className);
514 if (!classDef)
515 return symbolTable.getOp()->emitError("unknown class name ") << className;
516
517 // If this is an external class, create an ObjectValue and mark it unknown
518 if (isa<ClassExternOp>(classDef)) {
520 std::make_shared<evaluator::ObjectValue>(classDef, loc);
521 attachCounter(result);
522 result->markUnknown();
523 LLVM_DEBUG(dbgs(1) << "extern: <unknown-value>\n");
524 return result;
525 }
526
527 // Otherwise, it's a regular class, proceed normally
528 ClassOp cls = cast<ClassOp>(classDef);
529
530 if (failed(verifyActualParameters(cls, *actualParams)))
531 return failure();
532
533 // Instantiate the fields.
535
536 auto *context = cls.getContext();
537 {
538 LLVM_DEBUG(dbgs() << "ops:\n");
539#ifndef NDEBUG
540 DebugNesting nestOne(debugNesting);
541#endif
542 for (auto &op : cls.getOps())
543 for (auto result : op.getResults()) {
544 // Allocate the value, with unknown loc. It will be later set when
545 // evaluating the fields.
546 if (failed(getOrCreateValue(result, actualParams,
547 UnknownLoc::get(context))))
548 return failure();
549 // Add to the worklist.
550 worklist.push_back({result, actualParams});
551 }
552 }
553
554 LLVM_DEBUG(dbgs() << "fields:\n");
555 auto fieldNames = cls.getFieldNames();
556 auto operands = cls.getFieldsOp()->getOperands();
557 for (size_t i = 0; i < fieldNames.size(); ++i) {
558 auto name = fieldNames[i];
559 auto value = operands[i];
560 auto fieldLoc = cls.getFieldLocByIndex(i);
561 LLVM_DEBUG(dbgs() << "- name: " << name << "\n"
562 << indent(1) << "evaluate:\n");
563#ifndef NDEBUG
564 DebugNesting nestOne(debugNesting);
565#endif
566 FailureOr<evaluator::EvaluatorValuePtr> result =
567 evaluateValue(value, actualParams, fieldLoc);
568 if (failed(result))
569 return result;
570
571 LLVM_DEBUG(dbgs() << "value: " << result.value() << "\n");
572 fields[cast<StringAttr>(name)] = result.value();
573 }
574
575 // Defer property assertions until after the worklist is drained, so that
576 // all ReferenceValues are fully resolved before we try to inspect them.
577 LLVM_DEBUG(dbgs() << "queuing asserts:\n");
578 for (auto assertOp : cls.getOps<PropertyAssertOp>()) {
579 LLVM_DEBUG(dbgs(1) << "- " << assertOp << "\n");
580 pendingAsserts.push({assertOp, actualParams});
581 }
582
583 // If the there is an instance, we must update the object value.
584 LLVM_DEBUG(dbgs() << "object value:\n");
585 if (instanceKey.first) {
586 auto result =
587 getOrCreateValue(instanceKey.first, instanceKey.second, loc).value();
588 auto *object = llvm::cast<evaluator::ObjectValue>(result.get());
589 object->setFields(std::move(fields));
590 return result;
591 }
592
593 // If it's external call, just allocate new ObjectValue.
595 std::make_shared<evaluator::ObjectValue>(cls, fields, loc);
596 // Object is already fully evaluated when created with fields.
597 assert(result->isFullyEvaluated() &&
598 "object with fields should be fully evaluated");
599 return result;
600}
601
602/// Instantiate an Object with its class name and actual parameters.
603FailureOr<std::shared_ptr<evaluator::EvaluatorValue>>
605 StringAttr className, ArrayRef<evaluator::EvaluatorValuePtr> actualParams) {
606 LLVM_DEBUG(dbgs() << "instantiate:\n");
607#ifndef NDEBUG
608 DebugNesting nest(debugNesting);
609#endif
610 LLVM_DEBUG({
611 dbgs() << "class: " << className << "\n" << indent() << "params:\n";
612 for (auto &param : actualParams)
613 dbgs() << "- " << param << "\n";
614 });
615
616 // Skip the elaboration transform and directly instantiate the class if the
617 // caller explicitly requests so.
618 // TODO: Remove this after fully migrating to the new evaluator-based
619 // implementation.
620 if (getModule()->hasAttr(skipElaborationTransformAttr))
621 return instantiateImpl(className, actualParams);
622
623 auto rootClass = symbolTable.lookup<ClassLike>(className);
624 if (!rootClass)
625 return symbolTable.getOp()->emitError("unknown class name ") << className;
626 if (failed(verifyActualParameters(rootClass, actualParams)))
627 return failure();
628
629 ScratchIRBuilder scratchBuilder(getModule(), symbolTable, rootClass);
630 auto transformedInstantiation = scratchBuilder.run(actualParams);
631 if (failed(transformedInstantiation))
632 return failure();
633
634 auto wrapper = instantiateImpl(transformedInstantiation->className,
635 transformedInstantiation->actualParams);
636 if (failed(wrapper))
637 return failure();
638
639 auto root =
640 cast<evaluator::ObjectValue>(wrapper.value().get())->getField("root");
641 if (failed(root))
642 return failure();
643 return root.value();
644}
645
646FailureOr<std::shared_ptr<evaluator::EvaluatorValue>>
648 StringAttr className, ArrayRef<evaluator::EvaluatorValuePtr> actualParams) {
649 auto classDef = symbolTable.lookup<ClassLike>(className);
650 if (!classDef)
651 return symbolTable.getOp()->emitError("unknown class name ") << className;
652
653 // If this is an external class, create an ObjectValue and mark it unknown
654 if (isa<ClassExternOp>(classDef)) {
656 std::make_shared<evaluator::ObjectValue>(
657 classDef, UnknownLoc::get(classDef.getContext()));
658 attachCounter(result);
659 result->markUnknown();
660 LLVM_DEBUG(dbgs(1) << "result: <unknown extern>\n");
661 return result;
662 }
663
664 // Otherwise, it's a regular class, proceed normally
665 ClassOp cls = cast<ClassOp>(classDef);
666
667 auto parameters =
668 std::make_unique<SmallVector<std::shared_ptr<evaluator::EvaluatorValue>>>(
669 actualParams);
670
671 actualParametersBuffers.push_back(std::move(parameters));
672
673 auto loc = cls.getLoc();
674 LLVM_DEBUG(dbgs() << "evaluate object:\n");
675 auto result = evaluateObjectInstance(
676 className, actualParametersBuffers.back().get(), loc);
677
678 if (failed(result))
679 return failure();
680
681 // `evaluateObjectInstance` has populated the worklist. Continue evaluations
682 // unless there is a partially evaluated value.
683 LLVM_DEBUG(dbgs() << "worklist:\n");
684
685 // Use two-worklist approach: process all items from current worklist, and if
686 // at least one becomes fully evaluated, swap and continue. If a full pass
687 // completes with no progress, we have a cycle.
688 while (!worklist.empty()) {
689 uint64_t countBeforePass = fullyEvaluatedCount;
690 LLVM_DEBUG(dbgs() << "- processing " << worklist.size()
691 << " items (fully evaluated count: "
692 << fullyEvaluatedCount << ")\n");
693
694 // Process all items in the current worklist.
695 while (!worklist.empty()) {
696 auto [value, args] = worklist.back();
697 worklist.pop_back();
698 auto result = evaluateValue(value, args, loc);
699
700 if (failed(result))
701 return failure();
702
703 // If not fully evaluated, add to next worklist for retry.
704 if (!result.value()->isFullyEvaluated())
705 nextWorklist.push_back({value, args});
706 }
707
708 // Check if we made progress.
709 uint64_t evaluatedThisPass = fullyEvaluatedCount - countBeforePass;
710 LLVM_DEBUG(dbgs() << "- evaluated " << evaluatedThisPass
711 << " nodes this pass\n");
712
713 // If nothing became fully evaluated in this pass, we have a cycle.
714 if (evaluatedThisPass == 0 && !nextWorklist.empty())
715 return cls.emitError()
716 << "cycle detected: " << nextWorklist.size()
717 << " values remain partially evaluated after full pass with no "
718 "progress (total fully evaluated: "
719 << fullyEvaluatedCount << ")";
720
721 // Swap worklists for next iteration.
722 worklist = std::move(nextWorklist);
723 nextWorklist.clear();
724 }
725
726 // Now that all values are fully resolved, evaluate the deferred property
727 // assertions.
728 LLVM_DEBUG(dbgs() << "asserts:\n");
729 bool assertFailed = false;
730 while (!pendingAsserts.empty()) {
731 auto [assertOp, assertParams] = pendingAsserts.front();
732 pendingAsserts.pop();
733 assertFailed |= failed(evaluatePropertyAssert(assertOp, assertParams));
734 }
735 if (assertFailed)
736 return failure();
737
738 auto &object = result.value();
739 // Finalize the value. This will eliminate intermidiate ReferenceValue used as
740 // a placeholder in the initialization.
741 LLVM_DEBUG(dbgs() << "finalizing\n");
742 if (failed(object->finalize()))
743 return cls.emitError() << "failed to finalize evaluation. Probably the "
744 "class contains a dataflow cycle";
745 LLVM_DEBUG(dbgs() << "result: " << object << "\n");
746 return object;
747}
748
749FailureOr<evaluator::EvaluatorValuePtr>
751 Location loc) {
752 auto evaluatorValue = getOrCreateValue(value, actualParams, loc).value();
753
754 LLVM_DEBUG(dbgs() << "- eval: " << value << "\n");
755
756 // Return if the value is already evaluated.
757 if (evaluatorValue->isFullyEvaluated()) {
758 LLVM_DEBUG(dbgs(1) << "fully evaluated: " << evaluatorValue << "\n");
759 return evaluatorValue;
760 }
761
762 return llvm::TypeSwitch<Value, FailureOr<evaluator::EvaluatorValuePtr>>(value)
763 .Case([&](BlockArgument arg) {
764 return evaluateParameter(arg, actualParams, loc);
765 })
766 .Case([&](OpResult result) {
767 return TypeSwitch<Operation *, FailureOr<evaluator::EvaluatorValuePtr>>(
768 result.getDefiningOp())
769 .Case([&](ConstantOp op) {
770 return evaluateConstant(op, actualParams, loc);
771 })
772 .Case([&](IntegerBinaryOp op) {
773 return evaluateIntegerBinary(op, actualParams, loc);
774 })
775 .Case([&](ObjectOp op) {
776 return evaluateObjectInstance(op, actualParams);
777 })
778 .Case([&](ElaboratedObjectOp op) {
779 return evaluateElaboratedObject(op, actualParams, loc);
780 })
781 .Case([&](ObjectFieldOp op) {
782 return evaluateObjectField(op, actualParams, loc);
783 })
784 .Case([&](ListCreateOp op) {
785 return evaluateListCreate(op, actualParams, loc);
786 })
787 .Case([&](ListConcatOp op) {
788 return evaluateListConcat(op, actualParams, loc);
789 })
790 .Case([&](StringConcatOp op) {
791 return evaluateStringConcat(op, actualParams, loc);
792 })
793 .Case([&](BinaryEqualityOp op) {
794 return evaluateBinaryEquality(op, actualParams, loc);
795 })
796 .Case([&](AnyCastOp op) {
797 return evaluateValue(op.getInput(), actualParams, loc);
798 })
799 .Case([&](FrozenBasePathCreateOp op) {
800 return evaluateBasePathCreate(op, actualParams, loc);
801 })
802 .Case([&](FrozenPathCreateOp op) {
803 return evaluatePathCreate(op, actualParams, loc);
804 })
805 .Case([&](FrozenEmptyPathOp op) {
806 return evaluateEmptyPath(op, actualParams, loc);
807 })
808 .Case<UnknownValueOp>([&](UnknownValueOp op) {
809 return evaluateUnknownValue(op, loc);
810 })
811 .Default([&](Operation *op) {
812 auto error = op->emitError("unable to evaluate value");
813 error.attachNote() << "value: " << value;
814 return error;
815 });
816 });
817}
818
819/// Evaluator dispatch function for parameters.
820FailureOr<evaluator::EvaluatorValuePtr> circt::om::Evaluator::evaluateParameter(
821 BlockArgument formalParam, ActualParameters actualParams, Location loc) {
822 auto val = (*actualParams)[formalParam.getArgNumber()];
823 val->setLoc(loc);
824 return success(val);
825}
826
827/// Evaluator dispatch function for constants.
828FailureOr<circt::om::evaluator::EvaluatorValuePtr>
830 ActualParameters actualParams,
831 Location loc) {
832 // For list constants, create ListValue.
833 return success(om::evaluator::AttributeValue::get(op.getValue(), loc));
834}
835
836// Evaluator dispatch function for integer binary operations.
838 IntegerBinaryOp op, ActualParameters actualParams, Location loc) {
839 // Get the op's EvaluatorValue handle, in case it hasn't been evaluated yet.
840 auto handle = getOrCreateValue(op.getResult(), actualParams, loc);
841
842 // If it's fully evaluated, we can return it.
843 if (handle.value()->isFullyEvaluated())
844 return handle;
845
846 // Evaluate operands if necessary, and return the partially evaluated value if
847 // they aren't ready.
848 auto lhsResult = evaluateValue(op.getLhs(), actualParams, loc);
849 if (failed(lhsResult))
850 return lhsResult;
851 if (!lhsResult.value()->isFullyEvaluated())
852 return handle;
853
854 auto rhsResult = evaluateValue(op.getRhs(), actualParams, loc);
855 if (failed(rhsResult))
856 return rhsResult;
857 if (!rhsResult.value()->isFullyEvaluated())
858 return handle;
859
860 // Check if any operand is unknown and propagate the unknown flag.
861 if (lhsResult.value()->isUnknown() || rhsResult.value()->isUnknown()) {
862 handle.value()->markUnknown();
863 return handle;
864 }
865
866 // Extract the attribute from an EvaluatorValue (handles both om::IntegerAttr
867 // and mlir::IntegerAttr).
868 auto extractAttr = [](evaluator::EvaluatorValue *value) -> Attribute {
869 return llvm::TypeSwitch<evaluator::EvaluatorValue *, Attribute>(value)
870 .Case([](evaluator::AttributeValue *val) { return val->getAttr(); })
871 .Case([](evaluator::ReferenceValue *val) {
872 return cast<evaluator::AttributeValue>(val->getStrippedValue()->get())
873 ->getAttr();
874 });
875 };
876
877 mlir::Attribute lhsAttr = extractAttr(lhsResult.value().get());
878 mlir::Attribute rhsAttr = extractAttr(rhsResult.value().get());
879 assert(lhsAttr && rhsAttr &&
880 "expected attribute for IntegerBinaryOp operands");
881
882 std::array<Attribute, 2> operandAttrs = {lhsAttr, rhsAttr};
883 SmallVector<mlir::OpFoldResult, 1> results;
884 mlir::Attribute resultAttr;
885 // Even with fully constant operands, folders may decline to fold or may
886 // produce a non-attribute result.
887 if (failed(op->fold(operandAttrs, results)) || results.size() != 1 ||
888 !(resultAttr = results[0].dyn_cast<Attribute>()))
889 return op->emitError("failed to evaluate integer operation");
890
891 // Finalize the op result value.
892 auto *handleValue = cast<evaluator::AttributeValue>(handle.value().get());
893 auto resultStatus = handleValue->setAttr(resultAttr);
894 if (failed(resultStatus))
895 return resultStatus;
896
897 auto finalizeStatus = handleValue->finalize();
898 if (failed(finalizeStatus))
899 return finalizeStatus;
900
901 return handle;
902}
903
904/// Evaluator dispatch function for property assertions.
905LogicalResult
907 ActualParameters actualParams) {
908#ifndef NDEBUG
909 DebugNesting nest(debugNesting);
910#endif
911
912 auto loc = op.getLoc();
913
914 // Evaluate the condition, returning early if it isn't ready yet.
915 LLVM_DEBUG(dbgs() << "op: " << op << "\n"
916 << indent() << "evaluate condition: \n");
917 auto condResult = evaluateValue(op.getCondition(), actualParams, loc);
918 if (failed(condResult))
919 return failure();
920 if (!condResult.value()->isFullyEvaluated()) {
921 LLVM_DEBUG(dbgs() << "evaluate condition: <not fully evaluated>\n");
922 return success();
923 }
924
925 // If the condition is unknown, skip silently (best-effort).
926 if (condResult.value()->isUnknown())
927 return success();
928
929 LLVM_DEBUG(dbgs() << "condition: " << condResult.value() << "\n");
930
931 // Extract the attribute from the condition value, handling the case where
932 // the condition resolves through a ReferenceValue (e.g. an ObjectFieldOp or
933 // a parameter that participates in cycle resolution).
934 auto extractAttr = [](evaluator::EvaluatorValue *value) -> mlir::Attribute {
935 return llvm::TypeSwitch<evaluator::EvaluatorValue *, mlir::Attribute>(value)
936 .Case([](evaluator::AttributeValue *val) { return val->getAttr(); })
937 .Case([](evaluator::ReferenceValue *val) -> mlir::Attribute {
938 auto stripped = val->getStrippedValue();
939 if (failed(stripped))
940 return {};
941 if (auto *attr =
942 dyn_cast<evaluator::AttributeValue>(stripped.value().get()))
943 return attr->getAttr();
944 return {};
945 })
946 .Default([](auto *) -> mlir::Attribute { return {}; });
947 };
948
949 auto condAttr = extractAttr(condResult.value().get());
950 if (!condAttr)
951 return success();
952
953 bool isFalse = false;
954 if (auto boolAttr = dyn_cast<BoolAttr>(condAttr))
955 isFalse = !boolAttr.getValue();
956 else if (auto intAttr = dyn_cast<mlir::IntegerAttr>(condAttr))
957 isFalse = intAttr.getValue().isZero();
958 else
959 return op.emitError("expected BoolAttr or mlir::IntegerAttr");
960
961 if (isFalse)
962 return op.emitError("OM property assertion failed: ") << op.getMessage();
963
964 return success();
965}
966
967/// Evaluator dispatch function for Object instances.
968FailureOr<circt::om::Evaluator::ActualParameters>
970 ValueRange range, ActualParameters actualParams, Location loc) {
971 // Create an unique storage to store parameters.
972 auto parameters = std::make_unique<
973 SmallVector<std::shared_ptr<evaluator::EvaluatorValue>>>();
974
975 // Collect operands' evaluator values in the current instantiation context.
976 for (auto input : range) {
977 auto inputResult = getOrCreateValue(input, actualParams, loc);
978 if (failed(inputResult))
979 return failure();
980 parameters->push_back(inputResult.value());
981 }
982
983 actualParametersBuffers.push_back(std::move(parameters));
984 return actualParametersBuffers.back().get();
985}
986
987/// Evaluator dispatch function for Object instances.
988FailureOr<evaluator::EvaluatorValuePtr>
990 ActualParameters actualParams) {
991 auto loc = op.getLoc();
992 if (isFullyEvaluated({op, actualParams}))
993 return getOrCreateValue(op, actualParams, loc);
994
995 auto params =
996 createParametersFromOperands(op.getOperands(), actualParams, loc);
997 if (failed(params))
998 return failure();
999 return evaluateObjectInstance(op.getClassNameAttr().getAttr(), params.value(),
1000 loc, {op, actualParams});
1001}
1002
1003FailureOr<evaluator::EvaluatorValuePtr>
1005 ActualParameters actualParams,
1006 Location loc) {
1007 auto objectValue = getOrCreateValue(op, actualParams, loc);
1008 if (failed(objectValue))
1009 return failure();
1010 auto object = cast<evaluator::ObjectValue>(objectValue.value().get());
1011 if (object->isFullyEvaluated())
1012 return objectValue;
1013
1014 auto classLike =
1015 symbolTable.lookup<ClassLike>(op.getClassNameAttr().getAttr());
1016 if (!classLike)
1017 return symbolTable.getOp()->emitError("unknown class name ")
1018 << op.getClassNameAttr();
1019
1020 auto fieldNames = classLike.getFieldNames();
1021 auto fieldValues = op.getFieldValues();
1022 if (fieldNames.size() != fieldValues.size())
1023 return op.emitError("field value list doesn't match class field list, "
1024 "expected ")
1025 << fieldNames.size() << " values but got " << fieldValues.size();
1026
1028 auto classOp = dyn_cast<ClassOp>(classLike.getOperation());
1029 for (auto [index, fieldNameAndValue] :
1030 llvm::enumerate(llvm::zip(fieldNames, fieldValues))) {
1031 auto [fieldName, fieldValue] = fieldNameAndValue;
1032 auto fieldLoc = classOp ? classOp.getFieldLocByIndex(index) : loc;
1033 auto fieldResult = getOrCreateValue(fieldValue, actualParams, fieldLoc);
1034 if (failed(fieldResult))
1035 return failure();
1036
1037 if (!fieldResult.value()->isFullyEvaluated())
1038 worklist.push_back({fieldValue, actualParams});
1039
1040 fields[cast<StringAttr>(fieldName)] = fieldResult.value();
1041 }
1042
1043 object->setFields(std::move(fields));
1044 return objectValue;
1045}
1046
1047/// Evaluator dispatch function for Object fields.
1048FailureOr<evaluator::EvaluatorValuePtr>
1050 ActualParameters actualParams,
1051 Location loc) {
1052 // Evaluate the Object itself, in case it hasn't been evaluated yet.
1053 FailureOr<evaluator::EvaluatorValuePtr> currentObjectResult =
1054 evaluateValue(op.getObject(), actualParams, loc);
1055 if (failed(currentObjectResult))
1056 return currentObjectResult;
1057
1058 auto result = currentObjectResult.value();
1059
1060 auto objectFieldValue = getOrCreateValue(op, actualParams, loc).value();
1061
1062 if (result->isUnknown()) {
1063 // If objectFieldValue is a ReferenceValue, set its value to a unknown value
1064 // of the proper type
1065 if (auto *ref =
1066 llvm::dyn_cast<evaluator::ReferenceValue>(objectFieldValue.get())) {
1067 auto unknownField = createUnknownValue(op.getResult().getType(), loc);
1068 if (failed(unknownField))
1069 return unknownField;
1070 ref->setValue(unknownField.value());
1071 }
1072 // markUnknown() also marks the value as fully evaluated
1073 objectFieldValue->markUnknown();
1074 return objectFieldValue;
1075 }
1076
1077 // If the result is a ReferenceValue, dereference it to get the actual object.
1078 if (auto *ref = llvm::dyn_cast<evaluator::ReferenceValue>(result.get())) {
1079 auto stripped = ref->getStrippedValue();
1080 if (failed(stripped))
1081 return failure();
1082 result = stripped.value();
1083 }
1084
1085 auto *currentObject = llvm::cast<evaluator::ObjectValue>(result.get());
1086
1087 auto field = op.getFieldAttr();
1088
1089 // `currentObject` might not be fully evaluated.
1090 if (!currentObject->getFields().contains(field))
1091 return objectFieldValue;
1092
1093 auto currentField = currentObject->getField(field);
1094 auto finalField = currentField.value();
1095
1096 if (!finalField->isFullyEvaluated())
1097 return objectFieldValue;
1098
1099 // Update the reference.
1100 llvm::cast<evaluator::ReferenceValue>(objectFieldValue.get())
1101 ->setValue(finalField);
1102
1103 // Return the field being accessed.
1104 return objectFieldValue;
1105}
1106
1107/// Evaluator dispatch function for List creation.
1108FailureOr<evaluator::EvaluatorValuePtr>
1110 ActualParameters actualParams,
1111 Location loc) {
1112 // Evaluate the Object itself, in case it hasn't been evaluated yet.
1113 SmallVector<evaluator::EvaluatorValuePtr> values;
1114 auto list = getOrCreateValue(op, actualParams, loc);
1115 bool hasUnknown = false;
1116 for (auto operand : op.getOperands()) {
1117 auto result = evaluateValue(operand, actualParams, loc);
1118 if (failed(result))
1119 return result;
1120 if (!result.value()->isFullyEvaluated())
1121 return list;
1122 // Check if any operand is unknown.
1123 if (result.value()->isUnknown())
1124 hasUnknown = true;
1125 values.push_back(result.value());
1126 }
1127
1128 // Set the list elements (this also marks the list as fully evaluated).
1129 llvm::cast<evaluator::ListValue>(list.value().get())
1130 ->setElements(std::move(values));
1131
1132 // If any operand is unknown, mark the list as unknown.
1133 // markUnknown() checks if already fully evaluated before calling
1134 // markFullyEvaluated().
1135 if (hasUnknown)
1136 list.value()->markUnknown();
1137
1138 return list;
1139}
1140
1141/// Evaluator dispatch function for List concatenation.
1142FailureOr<evaluator::EvaluatorValuePtr>
1144 ActualParameters actualParams,
1145 Location loc) {
1146 // Evaluate the List concat op itself, in case it hasn't been evaluated yet.
1147 SmallVector<evaluator::EvaluatorValuePtr> values;
1148 auto list = getOrCreateValue(op, actualParams, loc);
1149
1150 // Extract the ListValue, either directly or through an object reference.
1151 auto extractList = [](evaluator::EvaluatorValue *value) {
1152 return std::move(
1153 llvm::TypeSwitch<evaluator::EvaluatorValue *, evaluator::ListValue *>(
1154 value)
1155 .Case([](evaluator::ListValue *val) { return val; })
1156 .Case([](evaluator::ReferenceValue *val) {
1157 return cast<evaluator::ListValue>(val->getStrippedValue()->get());
1158 }));
1159 };
1160
1161 bool hasUnknown = false;
1162 for (auto operand : op.getOperands()) {
1163 auto result = evaluateValue(operand, actualParams, loc);
1164 if (failed(result))
1165 return result;
1166 if (!result.value()->isFullyEvaluated())
1167 return list;
1168 // Check if any operand is unknown.
1169 if (result.value()->isUnknown())
1170 hasUnknown = true;
1171
1172 // Extract this sublist and ensure it's done evaluating.
1173 evaluator::ListValue *subList = extractList(result.value().get());
1174 if (!subList->isFullyEvaluated())
1175 return list;
1176
1177 // Append each EvaluatorValue from the sublist.
1178 for (const auto &subValue : subList->getElements())
1179 values.push_back(subValue);
1180 }
1181
1182 // Return the concatenated list.
1183 llvm::cast<evaluator::ListValue>(list.value().get())
1184 ->setElements(std::move(values));
1185
1186 // If any operand is unknown, mark the result as unknown.
1187 // markUnknown() checks if already fully evaluated before calling
1188 // markFullyEvaluated().
1189 if (hasUnknown)
1190 list.value()->markUnknown();
1191
1192 return list;
1193}
1194
1195/// Evaluator dispatch function for String concatenation.
1196FailureOr<evaluator::EvaluatorValuePtr>
1198 ActualParameters actualParams,
1199 Location loc) {
1200 // Get the op's EvaluatorValue handle, in case it hasn't been evaluated yet.
1201 auto handle = getOrCreateValue(op.getResult(), actualParams, loc);
1202 if (failed(handle))
1203 return handle;
1204
1205 // If it's fully evaluated, we can return it.
1206 if (handle.value()->isFullyEvaluated())
1207 return handle;
1208
1209 // Extract the string attributes, handling both AttributeValue and
1210 // ReferenceValue cases.
1211 auto extractAttr = [](evaluator::EvaluatorValue *value) -> StringAttr {
1212 return llvm::TypeSwitch<evaluator::EvaluatorValue *, StringAttr>(value)
1213 .Case([](evaluator::AttributeValue *val) {
1214 return val->getAs<StringAttr>();
1215 })
1216 .Case([](evaluator::ReferenceValue *val) {
1217 return cast<evaluator::AttributeValue>(val->getStrippedValue()->get())
1218 ->getAs<StringAttr>();
1219 });
1220 };
1221
1222 // Evaluate all operands and concatenate them.
1223 std::string result;
1224 for (auto operand : op.getOperands()) {
1225 auto operandResult = evaluateValue(operand, actualParams, loc);
1226 if (failed(operandResult))
1227 return operandResult;
1228 if (!operandResult.value()->isFullyEvaluated())
1229 return handle;
1230
1231 StringAttr str = extractAttr(operandResult.value().get());
1232 assert(str && "expected StringAttr for StringConcatOp operand");
1233 result += str.getValue().str();
1234 }
1235
1236 // Create the concatenated string attribute.
1237 auto resultStr = StringAttr::get(result, op.getResult().getType());
1238
1239 // Finalize the op result value.
1240 auto *handleValue = cast<evaluator::AttributeValue>(handle.value().get());
1241 auto resultStatus = handleValue->setAttr(resultStr);
1242 if (failed(resultStatus))
1243 return resultStatus;
1244
1245 auto finalizeStatus = handleValue->finalize();
1246 if (failed(finalizeStatus))
1247 return finalizeStatus;
1248
1249 return handle;
1250}
1251
1252// Evaluator dispatch function for binary property equality operations.
1253FailureOr<evaluator::EvaluatorValuePtr>
1255 ActualParameters actualParams,
1256 Location loc) {
1257 // Get the op's EvaluatorValue handle, in case it hasn't been evaluated yet.
1258 auto handle = getOrCreateValue(op.getResult(), actualParams, loc);
1259 if (failed(handle))
1260 return handle;
1261
1262 // If it's fully evaluated, we can return it.
1263 if (handle.value()->isFullyEvaluated())
1264 return handle;
1265
1266 // Evaluate both operands, returning the partially evaluated handle if either
1267 // isn't ready yet.
1268 auto lhsResult = evaluateValue(op.getLhs(), actualParams, loc);
1269 if (failed(lhsResult))
1270 return lhsResult;
1271 if (!lhsResult.value()->isFullyEvaluated())
1272 return handle;
1273
1274 auto rhsResult = evaluateValue(op.getRhs(), actualParams, loc);
1275 if (failed(rhsResult))
1276 return rhsResult;
1277 if (!rhsResult.value()->isFullyEvaluated())
1278 return handle;
1279
1280 // Check if any operand is unknown and propagate the unknown flag.
1281 if (lhsResult.value()->isUnknown() || rhsResult.value()->isUnknown()) {
1282 handle.value()->markUnknown();
1283 return handle;
1284 }
1285
1286 // Extract the underlying attribute, handling both AttributeValue and
1287 // ReferenceValue cases.
1288 auto extractAttr = [](evaluator::EvaluatorValue *value) -> mlir::Attribute {
1289 return llvm::TypeSwitch<evaluator::EvaluatorValue *, mlir::Attribute>(value)
1290 .Case([](evaluator::AttributeValue *val) { return val->getAttr(); })
1291 .Case([](evaluator::ReferenceValue *val) -> mlir::Attribute {
1292 return cast<evaluator::AttributeValue>(val->getStrippedValue()->get())
1293 ->getAttr();
1294 });
1295 };
1296
1297 mlir::Attribute lhs = extractAttr(lhsResult.value().get());
1298 mlir::Attribute rhs = extractAttr(rhsResult.value().get());
1299 assert(lhs && rhs && "expected attribute for BinaryEqualityOp operands");
1300
1301 // Perform the binary equality operation.
1302 FailureOr<mlir::Attribute> result = op.evaluateBinaryEquality(lhs, rhs);
1303 if (failed(result))
1304 return op->emitError("failed to evaluate binary equality operation");
1305
1306 // Finalize the op result value.
1307 auto *handleValue = cast<evaluator::AttributeValue>(handle.value().get());
1308 auto resultStatus = handleValue->setAttr(*result);
1309 if (failed(resultStatus))
1310 return resultStatus;
1311
1312 auto finalizeStatus = handleValue->finalize();
1313 if (failed(finalizeStatus))
1314 return finalizeStatus;
1315
1316 return handle;
1317}
1318
1319FailureOr<evaluator::EvaluatorValuePtr>
1321 ActualParameters actualParams,
1322 Location loc) {
1323 // Evaluate the Object itself, in case it hasn't been evaluated yet.
1324 auto valueResult = getOrCreateValue(op, actualParams, loc).value();
1325 auto *path = llvm::cast<evaluator::BasePathValue>(valueResult.get());
1326 auto result = evaluateValue(op.getBasePath(), actualParams, loc);
1327 if (failed(result))
1328 return result;
1329 auto &value = result.value();
1330 if (!value->isFullyEvaluated())
1331 return valueResult;
1332
1333 // If the base path is unknown, mark the result as unknown.
1334 if (result.value()->isUnknown()) {
1335 valueResult->markUnknown();
1336 return valueResult;
1337 }
1338
1339 path->setBasepath(*llvm::cast<evaluator::BasePathValue>(value.get()));
1340 return valueResult;
1341}
1342
1343FailureOr<evaluator::EvaluatorValuePtr>
1345 ActualParameters actualParams,
1346 Location loc) {
1347 // Evaluate the Object itself, in case it hasn't been evaluated yet.
1348 auto valueResult = getOrCreateValue(op, actualParams, loc).value();
1349 auto *path = llvm::cast<evaluator::PathValue>(valueResult.get());
1350 auto result = evaluateValue(op.getBasePath(), actualParams, loc);
1351 if (failed(result))
1352 return result;
1353 auto &value = result.value();
1354 if (!value->isFullyEvaluated())
1355 return valueResult;
1356
1357 // If the base path is unknown, mark the result as unknown.
1358 if (result.value()->isUnknown()) {
1359 valueResult->markUnknown();
1360 return valueResult;
1361 }
1362
1363 path->setBasepath(*llvm::cast<evaluator::BasePathValue>(value.get()));
1364 return valueResult;
1365}
1366
1367FailureOr<evaluator::EvaluatorValuePtr> circt::om::Evaluator::evaluateEmptyPath(
1368 FrozenEmptyPathOp op, ActualParameters actualParams, Location loc) {
1369 auto valueResult = getOrCreateValue(op, actualParams, loc).value();
1370 return valueResult;
1371}
1372
1373/// Create an unknown value of the specified type
1374FailureOr<evaluator::EvaluatorValuePtr>
1376 using namespace circt::om::evaluator;
1377
1378 // Create an unknown value of the appropriate type by switching on the type
1379 auto result =
1380 TypeSwitch<Type, FailureOr<EvaluatorValuePtr>>(type)
1381 .Case([&](ListType type) -> FailureOr<EvaluatorValuePtr> {
1382 // Create an empty list
1383 return success(std::make_shared<ListValue>(type, loc));
1384 })
1385 .Case([&](ClassType type) -> FailureOr<EvaluatorValuePtr> {
1386 // Look up the class definition
1387 auto classDef =
1388 symbolTable.lookup<ClassLike>(type.getClassName().getValue());
1389 if (!classDef)
1390 return symbolTable.getOp()->emitError("unknown class name ")
1391 << type.getClassName();
1392
1393 // Create an ObjectValue for both ClassOp and ClassExternOp
1394 return success(std::make_shared<ObjectValue>(classDef, loc));
1395 })
1396 .Case([&](FrozenBasePathType type) -> FailureOr<EvaluatorValuePtr> {
1397 // Create an empty basepath
1398 return success(std::make_shared<BasePathValue>(type.getContext()));
1399 })
1400 .Case([&](FrozenPathType type) -> FailureOr<EvaluatorValuePtr> {
1401 // Create an empty path
1402 return success(
1403 std::make_shared<PathValue>(PathValue::getEmptyPath(loc)));
1404 })
1405 .Default([&](Type type) -> FailureOr<EvaluatorValuePtr> {
1406 // For all other types (primitives like integer, string,
1407 // etc.), create an AttributeValue
1408 return success(AttributeValue::get(type, LocationAttr(loc)));
1409 });
1410
1411 // Mark the result as unknown if successful.
1412 if (succeeded(result))
1413 result->get()->markUnknown();
1414
1415 return result;
1416}
1417
1418/// Evaluate an unknown value
1419FailureOr<evaluator::EvaluatorValuePtr>
1420circt::om::Evaluator::evaluateUnknownValue(UnknownValueOp op, Location loc) {
1421 return createUnknownValue(op.getType(), loc);
1422}
1423
1424//===----------------------------------------------------------------------===//
1425// ObjectValue
1426//===----------------------------------------------------------------------===//
1427
1428/// Get a field of the Object by name.
1429FailureOr<EvaluatorValuePtr>
1431 auto field = fields.find(name);
1432 if (field == fields.end())
1433 return cls.emitError("field ") << name << " does not exist";
1434 return success(fields[name]);
1435}
1436
1437/// Get an ArrayAttr with the names of the fields in the Object. Sort the fields
1438/// so there is always a stable order.
1440 SmallVector<Attribute> fieldNames;
1441 for (auto &f : fields)
1442 fieldNames.push_back(f.first);
1443
1444 llvm::sort(fieldNames, [](Attribute a, Attribute b) {
1445 return cast<StringAttr>(a).getValue() < cast<StringAttr>(b).getValue();
1446 });
1447
1448 return ArrayAttr::get(cls.getContext(), fieldNames);
1449}
1450
1452 for (auto &&[e, value] : fields)
1453 if (failed(finalizeEvaluatorValue(value)))
1454 return failure();
1455
1456 return success();
1457}
1458
1459//===----------------------------------------------------------------------===//
1460// ReferenceValue
1461//===----------------------------------------------------------------------===//
1462
1464 auto result = getStrippedValue();
1465 if (failed(result))
1466 return result;
1467 value = std::move(result.value());
1468 // the stripped value also needs to be finalized
1469 if (failed(finalizeEvaluatorValue(value)))
1470 return failure();
1471
1472 return success();
1473}
1474
1475//===----------------------------------------------------------------------===//
1476// ListValue
1477//===----------------------------------------------------------------------===//
1478
1480 for (auto &value : elements) {
1481 if (failed(finalizeEvaluatorValue(value)))
1482 return failure();
1483 }
1484 return success();
1485}
1486
1487//===----------------------------------------------------------------------===//
1488// BasePathValue
1489//===----------------------------------------------------------------------===//
1490
1492 : EvaluatorValue(context, Kind::BasePath, UnknownLoc::get(context)),
1493 path(PathAttr::get(context, {})) {
1494 markFullyEvaluated();
1495}
1496
1497evaluator::BasePathValue::BasePathValue(PathAttr path, Location loc)
1498 : EvaluatorValue(path.getContext(), Kind::BasePath, loc), path(path) {}
1499
1501 assert(isFullyEvaluated());
1502 return path;
1503}
1504
1506 assert(!isFullyEvaluated());
1507 auto newPath = llvm::to_vector(basepath.path.getPath());
1508 auto oldPath = path.getPath();
1509 newPath.append(oldPath.begin(), oldPath.end());
1510 path = PathAttr::get(path.getContext(), newPath);
1511 markFullyEvaluated();
1512}
1513
1514//===----------------------------------------------------------------------===//
1515// PathValue
1516//===----------------------------------------------------------------------===//
1517
1518evaluator::PathValue::PathValue(TargetKindAttr targetKind, PathAttr path,
1519 StringAttr module, StringAttr ref,
1520 StringAttr field, Location loc)
1521 : EvaluatorValue(loc.getContext(), Kind::Path, loc), targetKind(targetKind),
1522 path(path), module(module), ref(ref), field(field) {}
1523
1525 PathValue path(nullptr, nullptr, nullptr, nullptr, nullptr, loc);
1526 path.markFullyEvaluated();
1527 return path;
1528}
1529
1531 // If the module is null, then this is a path to a deleted object.
1532 if (!targetKind)
1533 return StringAttr::get(getContext(), "OMDeleted:");
1534 SmallString<64> result;
1535 switch (targetKind.getValue()) {
1536 case TargetKind::DontTouch:
1537 result += "OMDontTouchedReferenceTarget";
1538 break;
1539 case TargetKind::Instance:
1540 result += "OMInstanceTarget";
1541 break;
1542 case TargetKind::MemberInstance:
1543 result += "OMMemberInstanceTarget";
1544 break;
1545 case TargetKind::MemberReference:
1546 result += "OMMemberReferenceTarget";
1547 break;
1548 case TargetKind::Reference:
1549 result += "OMReferenceTarget";
1550 break;
1551 }
1552 result += ":~";
1553 if (!path.getPath().empty())
1554 result += path.getPath().front().module;
1555 else
1556 result += module.getValue();
1557 result += '|';
1558 for (const auto &elt : path) {
1559 result += elt.module.getValue();
1560 result += '/';
1561 result += elt.instance.getValue();
1562 result += ':';
1563 }
1564 if (!module.getValue().empty())
1565 result += module.getValue();
1566 if (!ref.getValue().empty()) {
1567 result += '>';
1568 result += ref.getValue();
1569 }
1570 if (!field.getValue().empty())
1571 result += field.getValue();
1572 return StringAttr::get(field.getContext(), result);
1573}
1574
1576 assert(!isFullyEvaluated());
1577 auto newPath = llvm::to_vector(basepath.getPath().getPath());
1578 auto oldPath = path.getPath();
1579 newPath.append(oldPath.begin(), oldPath.end());
1580 path = PathAttr::get(path.getContext(), newPath);
1581 markFullyEvaluated();
1582}
1583
1584//===----------------------------------------------------------------------===//
1585// AttributeValue
1586//===----------------------------------------------------------------------===//
1587
1589 if (cast<TypedAttr>(attr).getType() != this->type)
1590 return mlir::emitError(getLoc(), "cannot set AttributeValue of type ")
1591 << this->type << " to Attribute " << attr;
1592 if (isFullyEvaluated())
1593 return mlir::emitError(
1594 getLoc(),
1595 "cannot set AttributeValue that has already been fully evaluated");
1596 this->attr = attr;
1597 markFullyEvaluated();
1598 return success();
1599}
1600
1602 if (!isFullyEvaluated())
1603 return mlir::emitError(
1604 getLoc(), "cannot finalize AttributeValue that is not fully evaluated");
1605 return success();
1606}
1607
1608std::shared_ptr<evaluator::EvaluatorValue>
1609circt::om::evaluator::AttributeValue::get(Attribute attr, LocationAttr loc) {
1610 auto type = cast<TypedAttr>(attr).getType();
1611 auto *context = type.getContext();
1612 if (!loc)
1613 loc = UnknownLoc::get(context);
1614
1615 // Special handling for ListType to create proper ListValue objects instead of
1616 // AttributeValue objects.
1617 if (auto listType = dyn_cast<circt::om::ListType>(type)) {
1618 SmallVector<EvaluatorValuePtr> elements;
1619 auto listAttr = cast<om::ListAttr>(attr);
1621 listAttr.getContext(), listAttr.getElements().getValue());
1622 elements.append(values.begin(), values.end());
1623 auto list = std::make_shared<evaluator::ListValue>(listType, elements, loc);
1624 return list;
1625 }
1626
1627 return std::shared_ptr<AttributeValue>(
1628 new AttributeValue(PrivateTag{}, attr, loc));
1629}
1630
1631std::shared_ptr<evaluator::EvaluatorValue>
1632circt::om::evaluator::AttributeValue::get(Type type, LocationAttr loc) {
1633 auto *context = type.getContext();
1634 if (!loc)
1635 loc = UnknownLoc::get(context);
1636
1637 // Special handling for ListType to create proper ListValue objects instead of
1638 // AttributeValue objects.
1639 if (auto listType = dyn_cast<circt::om::ListType>(type))
1640 return std::make_shared<evaluator::ListValue>(listType, loc);
1641 // Create the AttributeValue with the private tag
1642 return std::shared_ptr<AttributeValue>(
1643 new AttributeValue(PrivateTag{}, type, loc));
1644}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:212
FailureOr< evaluator::EvaluatorValuePtr > evaluateBasePathCreate(FrozenBasePathCreateOp op, ActualParameters actualParams, Location loc)
FailureOr< EvaluatorValuePtr > evaluateElaboratedObject(ElaboratedObjectOp op, ActualParameters actualParams, Location loc)
FailureOr< evaluator::EvaluatorValuePtr > evaluateEmptyPath(FrozenEmptyPathOp op, ActualParameters actualParams, Location loc)
FailureOr< evaluator::EvaluatorValuePtr > getPartiallyEvaluatedValue(Type type, Location loc)
FailureOr< EvaluatorValuePtr > evaluateValue(Value value, ActualParameters actualParams, Location loc)
Evaluate a Value in a Class body according to the small expression grammar described in the rationale...
FailureOr< EvaluatorValuePtr > evaluateConstant(ConstantOp op, ActualParameters actualParams, Location loc)
Evaluator dispatch function for constants.
FailureOr< EvaluatorValuePtr > evaluateStringConcat(StringConcatOp op, ActualParameters actualParams, Location loc)
Evaluator dispatch function for String concatenation.
LogicalResult evaluatePropertyAssert(PropertyAssertOp op, ActualParameters actualParams)
Evaluator dispatch function for property assertions.
mlir::ModuleOp getModule()
Get the Module this Evaluator is built from.
FailureOr< EvaluatorValuePtr > evaluateObjectField(ObjectFieldOp op, ActualParameters actualParams, Location loc)
Evaluator dispatch function for Object fields.
FailureOr< evaluator::EvaluatorValuePtr > createUnknownValue(Type type, Location loc)
Create an unknown value of the specified type.
FailureOr< EvaluatorValuePtr > evaluateIntegerBinary(IntegerBinaryOp op, ActualParameters actualParams, Location loc)
FailureOr< evaluator::EvaluatorValuePtr > evaluateUnknownValue(UnknownValueOp op, Location loc)
Evaluate an unknown value.
Evaluator(ModuleOp mod)
Construct an Evaluator with an IR module.
FailureOr< evaluator::EvaluatorValuePtr > instantiate(StringAttr className, ArrayRef< EvaluatorValuePtr > actualParams)
Instantiate an Object with its class name and actual parameters.
FailureOr< EvaluatorValuePtr > getOrCreateValue(Value value, ActualParameters actualParams, Location loc)
SmallVectorImpl< std::shared_ptr< evaluator::EvaluatorValue > > * ActualParameters
Definition Evaluator.h:401
FailureOr< evaluator::EvaluatorValuePtr > instantiateImpl(StringAttr className, ArrayRef< EvaluatorValuePtr > actualParams)
FailureOr< EvaluatorValuePtr > evaluateBinaryEquality(BinaryEqualityOp op, ActualParameters actualParams, Location loc)
FailureOr< EvaluatorValuePtr > evaluateListCreate(ListCreateOp op, ActualParameters actualParams, Location loc)
Evaluator dispatch function for List creation.
FailureOr< EvaluatorValuePtr > evaluateListConcat(ListConcatOp op, ActualParameters actualParams, Location loc)
Evaluator dispatch function for List concatenation.
std::pair< Value, ActualParameters > ObjectKey
Definition Evaluator.h:403
FailureOr< EvaluatorValuePtr > evaluateParameter(BlockArgument formalParam, ActualParameters actualParams, Location loc)
Evaluator dispatch functions for the small expression grammar.
FailureOr< EvaluatorValuePtr > evaluateObjectInstance(StringAttr className, ActualParameters actualParams, Location loc, ObjectKey instanceKey={})
Instantiate an Object with its class name and actual parameters.
FailureOr< evaluator::EvaluatorValuePtr > evaluatePathCreate(FrozenPathCreateOp op, ActualParameters actualParams, Location loc)
FailureOr< ActualParameters > createParametersFromOperands(ValueRange range, ActualParameters actualParams, Location loc)
Evaluator dispatch function for Object instances.
Values which can be directly representable by MLIR attributes.
Definition Evaluator.h:161
LogicalResult setAttr(Attribute attr)
friend std::shared_ptr< EvaluatorValue > get(Attribute attr, LocationAttr loc)
static std::shared_ptr< EvaluatorValue > get(Attribute attr, LocationAttr loc={})
BasePathValue(MLIRContext *context)
void setBasepath(const BasePathValue &basepath)
Set the basepath which this path is relative to.
Base class for evaluator runtime values.
Definition Evaluator.h:49
A List which contains variadic length of elements with the same type.
Definition Evaluator.h:222
const auto & getElements() const
Definition Evaluator.h:243
om::ListType getListType() const
Return the type of the value, which is a ListType.
Definition Evaluator.h:246
A composite Object, which has a type and fields.
Definition Evaluator.h:259
FailureOr< EvaluatorValuePtr > getField(StringAttr field)
Get a field of the Object by name.
ArrayAttr getFieldNames()
Get all the field names of the Object.
om::ClassLike getClassOp() const
Definition Evaluator.h:271
void setBasepath(const BasePathValue &basepath)
PathValue(om::TargetKindAttr targetKind, om::PathAttr path, StringAttr module, StringAttr ref, StringAttr field, Location loc)
Create a path value representing a regular path.
static PathValue getEmptyPath(Location loc)
Values which can be used as pointers to different values.
Definition Evaluator.h:121
std::shared_ptr< EvaluatorValue > EvaluatorValuePtr
A value of an object in memory.
Definition Evaluator.h:39
evaluator::EvaluatorValuePtr EvaluatorValuePtr
Definition Evaluator.h:377
SmallVector< EvaluatorValuePtr > getEvaluatorValuesFromAttributes(MLIRContext *context, ArrayRef< Attribute > attributes)
RAII helper to increment/decrement debugNesting.
Definition Evaluator.h:530