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