CIRCT 24.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 (!expectedType)
201 return emitError(loc, "cannot import OM evaluator value without an "
202 "expected type");
203
204 // Keep any-typed values opaque at the wrapper boundary.
205 if (isa<AnyType>(expectedType))
206 return createWrapperArgument(value, loc, expectedType);
207
208 if (auto it = importedValues.find(value.get()); it != importedValues.end())
209 return it->second;
210
211 if (value->isUnknown()) {
212 OpBuilder builder(wrapperClass.getFieldsOp());
213 auto result = UnknownValueOp::create(builder, loc, expectedType);
214 importedValues[value.get()] = result.getResult();
215 return result.getResult();
216 }
217
218 return llvm::TypeSwitch<evaluator::EvaluatorValue *, FailureOr<Value>>(
219 value.get())
220 .Case([&](evaluator::AttributeValue *attrValue) -> FailureOr<Value> {
221 auto attr = attrValue->getAttr();
222 if (!attr)
223 return emitError(loc, "cannot import OM attribute value without an "
224 "attribute");
225
226 OpBuilder builder(wrapperClass.getFieldsOp());
227 auto result = ConstantOp::create(builder, loc, cast<TypedAttr>(attr));
228 importedValues[value.get()] = result.getResult();
229 return result.getResult();
230 })
231 .Case([&](evaluator::ListValue *listValue) {
232 return materializeListInput(listValue, loc);
233 })
234 .Case([&](evaluator::ObjectValue *objectValue) {
235 return materializeObjectInput(objectValue, loc);
236 })
237 .Default([&](evaluator::EvaluatorValue *) -> FailureOr<Value> {
238 auto result = createWrapperArgument(value, loc, expectedType);
239 if (succeeded(result))
240 importedValues[value.get()] = *result;
241 return result;
242 });
243}
244
245FailureOr<Value>
246ScratchIRBuilder::materializeListInput(evaluator::ListValue *listValue,
247 Location loc) {
248 if (!listValue->isFullyEvaluated())
249 return emitError(loc, "cannot import partially evaluated OM list value");
250
251 auto listType = listValue->getListType();
252 SmallVector<Value> elementValues;
253 elementValues.reserve(listValue->getElements().size());
254 for (const auto &elementValue : listValue->getElements()) {
255 auto materializedElement =
256 materializeInput(elementValue, loc, listType.getElementType());
257 if (failed(materializedElement))
258 return failure();
259 elementValues.push_back(*materializedElement);
260 }
261
262 OpBuilder builder(wrapperClass.getFieldsOp());
263 auto result = ListCreateOp::create(builder, loc, listType, elementValues);
264 importedValues[listValue] = result.getResult();
265 return result.getResult();
266}
267
268FailureOr<Value>
269ScratchIRBuilder::materializeObjectInput(evaluator::ObjectValue *objectValue,
270 Location loc) {
271 // TODO: Currently we only support importing object values that don't have
272 // mutual references with other object values in the inputs for the
273 // simplicity. We could construct mutually referencing object values with a
274 // backedge builder but currently we don't have a use case for that.
275 if (!activeObjectImports.insert(objectValue).second)
276 return emitError(loc, "cannot import mutually referential OM objects");
277
278 llvm::scope_exit popActiveObjectImport(
279 [&] { activeObjectImports.erase(objectValue); });
280
281 auto classLike = objectValue->getClassOp();
282 SmallVector<Value> fieldValues;
283 auto fieldNames = classLike.getFieldNames();
284 fieldValues.reserve(fieldNames.size());
285 for (auto fieldName : fieldNames) {
286 auto fieldNameAttr = cast<StringAttr>(fieldName);
287 auto field = objectValue->getField(fieldNameAttr);
288 if (failed(field))
289 return failure();
290 auto materializedField = materializeInput(
291 field.value(), loc, classLike.getFieldType(fieldNameAttr).value());
292 if (failed(materializedField))
293 return failure();
294 fieldValues.push_back(*materializedField);
295 }
296
297 OpBuilder builder(wrapperClass.getFieldsOp());
298 auto result =
299 ElaboratedObjectOp::create(builder, loc, classLike, fieldValues);
300 importedValues[objectValue] = result.getResult();
301 return result.getResult();
302}
303
304FailureOr<Value>
305ScratchIRBuilder::createWrapperArgument(EvaluatorValuePtr value, Location loc,
306 Type argType) {
307 Builder builder(module.getContext());
308 wrapperArgNames.push_back(
309 builder.getStringAttr(Twine("arg") + Twine(wrapperArgNames.size())));
310 wrapperActualParams.push_back(value);
311 return wrapperClass.getBodyBlock()->addArgument(argType, loc);
312}
313
314} // namespace
315
316/// Construct an Evaluator with an IR module.
317circt::om::Evaluator::Evaluator(ModuleOp mod) : symbolTable(mod) {}
318
319/// Get the Module this Evaluator is built from.
321 return cast<ModuleOp>(symbolTable.getOp());
322}
323
324SmallVector<evaluator::EvaluatorValuePtr>
326 ArrayRef<Attribute> attributes) {
327 SmallVector<evaluator::EvaluatorValuePtr> values;
328 values.reserve(attributes.size());
329 for (auto attr : attributes)
330 values.push_back(evaluator::AttributeValue::get(cast<TypedAttr>(attr)));
331 return values;
332}
333
335 return llvm::TypeSwitch<const EvaluatorValue *, Type>(this)
336 .Case<AttributeValue>([](auto *attr) -> Type { return attr->getType(); })
337 .Case<ObjectValue>([](auto *object) { return object->getObjectType(); })
338 .Case<ListValue>([](auto *list) { return list->getListType(); })
339 .Case<BasePathValue>(
340 [this](auto *tuple) { return FrozenBasePathType::get(ctx); })
341 .Case<PathValue>(
342 [this](auto *tuple) { return FrozenPathType::get(ctx); });
343}
344
345FailureOr<evaluator::EvaluatorValuePtr>
347 using namespace circt::om::evaluator;
348
349 auto result =
350 TypeSwitch<mlir::Type, FailureOr<evaluator::EvaluatorValuePtr>>(type)
351 .Case([&](circt::om::ListType type) {
353 std::make_shared<evaluator::ListValue>(type, loc);
354 return success(result);
355 })
356 .Case([&](circt::om::ClassType type)
357 -> FailureOr<evaluator::EvaluatorValuePtr> {
358 auto classDef =
359 symbolTable.lookup<ClassLike>(type.getClassName().getValue());
360 if (!classDef)
361 return symbolTable.getOp()->emitError("unknown class name ")
362 << type.getClassName();
363
364 // Create an ObjectValue for both ClassOp and ClassExternOp
366 std::make_shared<evaluator::ObjectValue>(classDef, loc);
367
368 return success(result);
369 })
370 .Case([&](circt::om::StringType type) {
373 return success(result);
374 })
375 .Default([&](auto type) { return failure(); });
376
377 return result;
378}
379
380FailureOr<evaluator::EvaluatorValuePtr> circt::om::Evaluator::getOrCreateValue(
381 Value value, ActualParameters actualParams, Location loc) {
382 LLVM_DEBUG(dbgs() << "- get: " << value << "\n");
383
384 auto it = objects.find(value);
385 if (it != objects.end()) {
386 auto evalVal = it->second;
387 evalVal->setLocIfUnknown(loc);
388 return evalVal;
389 }
390
391 FailureOr<evaluator::EvaluatorValuePtr> result =
392 TypeSwitch<Value, FailureOr<evaluator::EvaluatorValuePtr>>(value)
393 .Case([&](BlockArgument arg) {
394 return evaluateParameter(arg, actualParams, loc);
395 })
396 .Case([&](OpResult result) {
397 return TypeSwitch<Operation *,
398 FailureOr<evaluator::EvaluatorValuePtr>>(
399 result.getDefiningOp())
400 .Case([&](ConstantOp op) {
401 return evaluateConstant(op, actualParams, loc);
402 })
403 .Case<AnyCastOp>([&](AnyCastOp op) {
404 return getOrCreateValue(op.getInput(), actualParams, loc);
405 })
406 .Case<FrozenBasePathCreateOp>([&](FrozenBasePathCreateOp op) {
408 std::make_shared<evaluator::BasePathValue>(
409 op.getPathAttr(), loc);
410 return success(result);
411 })
412 .Case<FrozenPathCreateOp>([&](FrozenPathCreateOp op) {
414 std::make_shared<evaluator::PathValue>(
415 op.getTargetKindAttr(), op.getPathAttr(),
416 op.getModuleAttr(), op.getRefAttr(),
417 op.getFieldAttr(), loc);
418 return success(result);
419 })
420 .Case<FrozenEmptyPathOp>([&](FrozenEmptyPathOp op) {
422 std::make_shared<evaluator::PathValue>(
424 return success(result);
425 })
426 .Case<ListCreateOp, ListConcatOp>([&](auto op) {
427 return getPartiallyEvaluatedValue(op.getType(), loc);
428 })
429 .Case<ElaboratedObjectOp>([&](auto op) {
430 return getPartiallyEvaluatedValue(op.getType(), op.getLoc());
431 })
432 .Case<UnknownValueOp>(
433 [&](auto op) { return evaluateUnknownValue(op, loc); })
434 .Default([&](Operation *op) {
435 auto error = op->emitError("unable to evaluate value");
436 error.attachNote() << "value: " << value;
437 return error;
438 });
439 });
440 if (failed(result))
441 return result;
442
443 objects[value] = result.value();
444 return result;
445}
446
447FailureOr<evaluator::EvaluatorValuePtr>
449 ActualParameters actualParams,
450 Location loc) {
451#ifndef NDEBUG
452 DebugNesting nestOne(debugNesting);
453#endif
454 LLVM_DEBUG(dbgs() << "object:\n");
455#ifndef NDEBUG
456 DebugNesting nestTwo(debugNesting);
457#endif
458 LLVM_DEBUG(dbgs() << "name: " << className << "\n");
459
460 auto classDef = symbolTable.lookup<ClassLike>(className);
461 if (!classDef)
462 return symbolTable.getOp()->emitError("unknown class name ") << className;
463
464 // If this is an external class, create an ObjectValue and mark it unknown
465 if (isa<ClassExternOp>(classDef)) {
467 std::make_shared<evaluator::ObjectValue>(classDef, loc);
468 result->markUnknown();
469 LLVM_DEBUG(dbgs(1) << "extern: <unknown-value>\n");
470 return result;
471 }
472
473 // Otherwise, it's a regular class, proceed normally
474 ClassOp cls = cast<ClassOp>(classDef);
475
476 if (failed(verifyActualParameters(cls, actualParams)))
477 return failure();
478
479 // Instantiate the fields.
481
482 auto *context = cls.getContext();
483 {
484 LLVM_DEBUG(dbgs() << "ops:\n");
485#ifndef NDEBUG
486 DebugNesting nestOne(debugNesting);
487#endif
488 // Allocate placeholders for all class-body results before evaluating any
489 // fields. This allows later operations to refer to earlier or later
490 // results without requiring a retry worklist.
491 for (auto &op : cls.getOps())
492 for (auto result : op.getResults())
493 if (failed(getOrCreateValue(result, actualParams,
494 UnknownLoc::get(context))))
495 return failure();
496
497 // Evaluate every operation after all placeholders have been allocated.
498 for (auto &op : cls.getOps())
499 for (auto result : op.getResults()) {
500 auto evaluated = evaluateValue(result, actualParams, op.getLoc());
501 if (failed(evaluated))
502 return failure();
503 }
504 }
505
506 LLVM_DEBUG(dbgs() << "fields:\n");
507 auto fieldNames = cls.getFieldNames();
508 auto operands = cls.getFieldsOp()->getOperands();
509 for (size_t i = 0; i < fieldNames.size(); ++i) {
510 auto name = fieldNames[i];
511 auto value = operands[i];
512 auto fieldLoc = cls.getFieldLocByIndex(i);
513 LLVM_DEBUG(dbgs() << "- name: " << name << "\n"
514 << indent(1) << "evaluate:\n");
515#ifndef NDEBUG
516 DebugNesting nestOne(debugNesting);
517#endif
518 FailureOr<evaluator::EvaluatorValuePtr> result =
519 evaluateValue(value, actualParams, fieldLoc);
520 if (failed(result))
521 return result;
522
523 LLVM_DEBUG(dbgs() << "value: " << result.value() << "\n");
524 fields[cast<StringAttr>(name)] = result.value();
525 }
526
527 // If it's external call, just allocate new ObjectValue.
529 std::make_shared<evaluator::ObjectValue>(cls, fields, loc);
530 // Object is already fully evaluated when created with fields.
531 assert(result->isFullyEvaluated() &&
532 "object with fields should be fully evaluated");
533 return result;
534}
535
536/// Instantiate an Object with its class name and actual parameters.
537FailureOr<std::shared_ptr<evaluator::EvaluatorValue>>
539 StringAttr className, ArrayRef<evaluator::EvaluatorValuePtr> actualParams) {
540 LLVM_DEBUG(dbgs() << "instantiate:\n");
541#ifndef NDEBUG
542 DebugNesting nest(debugNesting);
543#endif
544 LLVM_DEBUG({
545 dbgs() << "class: " << className << "\n" << indent() << "params:\n";
546 for (auto &param : actualParams)
547 dbgs() << "- " << param << "\n";
548 });
549
550 auto rootClass = symbolTable.lookup<ClassLike>(className);
551 if (!rootClass)
552 return symbolTable.getOp()->emitError("unknown class name ") << className;
553 if (failed(verifyActualParameters(rootClass, actualParams)))
554 return failure();
555
556 ScratchIRBuilder scratchBuilder(getModule(), symbolTable, rootClass);
557 auto transformedInstantiation = scratchBuilder.run(actualParams);
558 if (failed(transformedInstantiation))
559 return failure();
560
561 auto wrapper = instantiateImpl(transformedInstantiation->className,
562 transformedInstantiation->actualParams);
563 if (failed(wrapper))
564 return failure();
565
566 auto root =
567 cast<evaluator::ObjectValue>(wrapper.value().get())->getField("root");
568 if (failed(root))
569 return failure();
570 return root.value();
571}
572
573FailureOr<std::shared_ptr<evaluator::EvaluatorValue>>
575 StringAttr className, ArrayRef<evaluator::EvaluatorValuePtr> actualParams) {
576 auto classDef = symbolTable.lookup<ClassLike>(className);
577 if (!classDef)
578 return symbolTable.getOp()->emitError("unknown class name ") << className;
579
580 // If this is an external class, create an ObjectValue and mark it unknown
581 if (isa<ClassExternOp>(classDef)) {
583 std::make_shared<evaluator::ObjectValue>(
584 classDef, UnknownLoc::get(classDef.getContext()));
585 result->markUnknown();
586 LLVM_DEBUG(dbgs(1) << "result: <unknown extern>\n");
587 return result;
588 }
589
590 // Otherwise, it's a regular class, proceed normally
591 ClassOp cls = cast<ClassOp>(classDef);
592
593 auto loc = cls.getLoc();
594 LLVM_DEBUG(dbgs() << "evaluate object:\n");
595 auto result = evaluateObjectInstance(className, actualParams, loc);
596
597 if (failed(result))
598 return failure();
599
600 LLVM_DEBUG(dbgs() << "result: " << result.value() << "\n");
601 return result;
602}
603
604FailureOr<evaluator::EvaluatorValuePtr>
606 Location loc) {
607 auto evaluatorValue = getOrCreateValue(value, actualParams, loc).value();
608
609 LLVM_DEBUG(dbgs() << "- eval: " << value << "\n");
610
611 // Return if the value is already evaluated.
612 if (evaluatorValue->isFullyEvaluated()) {
613 LLVM_DEBUG(dbgs(1) << "fully evaluated: " << evaluatorValue << "\n");
614 return evaluatorValue;
615 }
616
617 return llvm::TypeSwitch<Value, FailureOr<evaluator::EvaluatorValuePtr>>(value)
618 .Case([&](BlockArgument arg) {
619 return evaluateParameter(arg, actualParams, loc);
620 })
621 .Case([&](OpResult result) {
622 return TypeSwitch<Operation *, FailureOr<evaluator::EvaluatorValuePtr>>(
623 result.getDefiningOp())
624 .Case([&](ConstantOp op) {
625 return evaluateConstant(op, actualParams, loc);
626 })
627 .Case([&](ElaboratedObjectOp op) {
628 return evaluateElaboratedObject(op, actualParams, loc);
629 })
630 .Case([&](ListCreateOp op) {
631 return evaluateListCreate(op, actualParams, loc);
632 })
633 .Case([&](ListConcatOp op) {
634 return evaluateListConcat(op, actualParams, loc);
635 })
636 .Case([&](AnyCastOp op) {
637 return evaluateValue(op.getInput(), actualParams, loc);
638 })
639 .Case([&](FrozenBasePathCreateOp op) {
640 return evaluateBasePathCreate(op, actualParams, loc);
641 })
642 .Case([&](FrozenPathCreateOp op) {
643 return evaluatePathCreate(op, actualParams, loc);
644 })
645 .Case([&](FrozenEmptyPathOp op) {
646 return evaluateEmptyPath(op, actualParams, loc);
647 })
648 .Case<UnknownValueOp>([&](UnknownValueOp op) {
649 return evaluateUnknownValue(op, loc);
650 })
651 .Default([&](Operation *op) {
652 auto error = op->emitError("unable to evaluate value");
653 error.attachNote() << "value: " << value;
654 return error;
655 });
656 });
657}
658
659/// Evaluator dispatch function for parameters.
660FailureOr<evaluator::EvaluatorValuePtr> circt::om::Evaluator::evaluateParameter(
661 BlockArgument formalParam, ActualParameters actualParams, Location loc) {
662 auto val = actualParams[formalParam.getArgNumber()];
663 val->setLoc(loc);
664 return success(val);
665}
666
667/// Evaluator dispatch function for constants.
668FailureOr<circt::om::evaluator::EvaluatorValuePtr>
670 ActualParameters actualParams,
671 Location loc) {
672 // For list constants, create ListValue.
673 return success(om::evaluator::AttributeValue::get(op.getValue(), loc));
674}
675
676FailureOr<evaluator::EvaluatorValuePtr>
678 ActualParameters actualParams,
679 Location loc) {
680 auto objectValue = getOrCreateValue(op, actualParams, loc);
681 if (failed(objectValue))
682 return failure();
683 auto object = cast<evaluator::ObjectValue>(objectValue.value().get());
684 if (object->isFullyEvaluated())
685 return objectValue;
686
687 auto classLike =
688 symbolTable.lookup<ClassLike>(op.getClassNameAttr().getAttr());
689 if (!classLike)
690 return symbolTable.getOp()->emitError("unknown class name ")
691 << op.getClassNameAttr();
692
693 auto fieldNames = classLike.getFieldNames();
694 auto fieldValues = op.getFieldValues();
695 if (fieldNames.size() != fieldValues.size())
696 return op.emitError("field value list doesn't match class field list, "
697 "expected ")
698 << fieldNames.size() << " values but got " << fieldValues.size();
699
701 auto classOp = dyn_cast<ClassOp>(classLike.getOperation());
702 for (auto [index, fieldNameAndValue] :
703 llvm::enumerate(llvm::zip(fieldNames, fieldValues))) {
704 auto [fieldName, fieldValue] = fieldNameAndValue;
705 auto fieldLoc = classOp ? classOp.getFieldLocByIndex(index) : loc;
706 auto fieldResult = getOrCreateValue(fieldValue, actualParams, fieldLoc);
707 if (failed(fieldResult))
708 return failure();
709
710 fields[cast<StringAttr>(fieldName)] = fieldResult.value();
711 }
712
713 object->setFields(std::move(fields));
714 return objectValue;
715}
716
717/// Evaluator dispatch function for List creation.
718FailureOr<evaluator::EvaluatorValuePtr>
720 ActualParameters actualParams,
721 Location loc) {
722 // Evaluate the Object itself, in case it hasn't been evaluated yet.
723 SmallVector<evaluator::EvaluatorValuePtr> values;
724 auto list = getOrCreateValue(op, actualParams, loc);
725 bool hasUnknown = false;
726 for (auto operand : op.getOperands()) {
727 auto result = evaluateValue(operand, actualParams, loc);
728 if (failed(result))
729 return result;
730 if (!result.value()->isFullyEvaluated())
731 return list;
732 // Check if any operand is unknown.
733 if (result.value()->isUnknown())
734 hasUnknown = true;
735 values.push_back(result.value());
736 }
737
738 // Set the list elements (this also marks the list as fully evaluated).
739 llvm::cast<evaluator::ListValue>(list.value().get())
740 ->setElements(std::move(values));
741
742 // If any operand is unknown, mark the list as unknown.
743 // markUnknown() checks if already fully evaluated before calling
744 // markFullyEvaluated().
745 if (hasUnknown)
746 list.value()->markUnknown();
747
748 return list;
749}
750
751/// Evaluator dispatch function for List concatenation.
752FailureOr<evaluator::EvaluatorValuePtr>
754 ActualParameters actualParams,
755 Location loc) {
756 // Evaluate the List concat op itself, in case it hasn't been evaluated yet.
757 SmallVector<evaluator::EvaluatorValuePtr> values;
758 auto list = getOrCreateValue(op, actualParams, loc);
759
760 bool hasUnknown = false;
761 for (auto operand : op.getOperands()) {
762 auto result = evaluateValue(operand, actualParams, loc);
763 if (failed(result))
764 return result;
765 if (!result.value()->isFullyEvaluated())
766 return list;
767 // Check if any operand is unknown.
768 if (result.value()->isUnknown())
769 hasUnknown = true;
770
771 auto *subList = llvm::cast<evaluator::ListValue>(result.value().get());
772
773 // Append each EvaluatorValue from the sublist.
774 for (const auto &subValue : subList->getElements())
775 values.push_back(subValue);
776 }
777
778 // Return the concatenated list.
779 llvm::cast<evaluator::ListValue>(list.value().get())
780 ->setElements(std::move(values));
781
782 // If any operand is unknown, mark the result as unknown.
783 // markUnknown() checks if already fully evaluated before calling
784 // markFullyEvaluated().
785 if (hasUnknown)
786 list.value()->markUnknown();
787
788 return list;
789}
790
791FailureOr<evaluator::EvaluatorValuePtr>
793 ActualParameters actualParams,
794 Location loc) {
795 // Evaluate the Object itself, in case it hasn't been evaluated yet.
796 auto valueResult = getOrCreateValue(op, actualParams, loc).value();
797 auto *path = llvm::cast<evaluator::BasePathValue>(valueResult.get());
798 auto result = evaluateValue(op.getBasePath(), actualParams, loc);
799 if (failed(result))
800 return result;
801 auto &value = result.value();
802 if (!value->isFullyEvaluated())
803 return valueResult;
804
805 // If the base path is unknown, mark the result as unknown.
806 if (result.value()->isUnknown()) {
807 valueResult->markUnknown();
808 return valueResult;
809 }
810
811 path->setBasepath(*llvm::cast<evaluator::BasePathValue>(value.get()));
812 return valueResult;
813}
814
815FailureOr<evaluator::EvaluatorValuePtr>
817 ActualParameters actualParams,
818 Location loc) {
819 // Evaluate the Object itself, in case it hasn't been evaluated yet.
820 auto valueResult = getOrCreateValue(op, actualParams, loc).value();
821 auto *path = llvm::cast<evaluator::PathValue>(valueResult.get());
822 auto result = evaluateValue(op.getBasePath(), actualParams, loc);
823 if (failed(result))
824 return result;
825 auto &value = result.value();
826 if (!value->isFullyEvaluated())
827 return valueResult;
828
829 // If the base path is unknown, mark the result as unknown.
830 if (result.value()->isUnknown()) {
831 valueResult->markUnknown();
832 return valueResult;
833 }
834
835 path->setBasepath(*llvm::cast<evaluator::BasePathValue>(value.get()));
836 return valueResult;
837}
838
839FailureOr<evaluator::EvaluatorValuePtr> circt::om::Evaluator::evaluateEmptyPath(
840 FrozenEmptyPathOp op, ActualParameters actualParams, Location loc) {
841 auto valueResult = getOrCreateValue(op, actualParams, loc).value();
842 return valueResult;
843}
844
845/// Create an unknown value of the specified type
846FailureOr<evaluator::EvaluatorValuePtr>
848 using namespace circt::om::evaluator;
849
850 // Create an unknown value of the appropriate type by switching on the type
851 auto result =
852 TypeSwitch<Type, FailureOr<EvaluatorValuePtr>>(type)
853 .Case([&](ListType type) -> FailureOr<EvaluatorValuePtr> {
854 // Create an empty list
855 return success(std::make_shared<ListValue>(type, loc));
856 })
857 .Case([&](ClassType type) -> FailureOr<EvaluatorValuePtr> {
858 // Look up the class definition
859 auto classDef =
860 symbolTable.lookup<ClassLike>(type.getClassName().getValue());
861 if (!classDef)
862 return symbolTable.getOp()->emitError("unknown class name ")
863 << type.getClassName();
864
865 // Create an ObjectValue for both ClassOp and ClassExternOp
866 return success(std::make_shared<ObjectValue>(classDef, loc));
867 })
868 .Case([&](FrozenBasePathType type) -> FailureOr<EvaluatorValuePtr> {
869 // Create an empty basepath
870 return success(std::make_shared<BasePathValue>(type.getContext()));
871 })
872 .Case([&](FrozenPathType type) -> FailureOr<EvaluatorValuePtr> {
873 // Create an empty path
874 return success(
875 std::make_shared<PathValue>(PathValue::getEmptyPath(loc)));
876 })
877 .Default([&](Type type) -> FailureOr<EvaluatorValuePtr> {
878 // For all other types (primitives like integer, string,
879 // etc.), create an AttributeValue
880 return success(AttributeValue::get(type, LocationAttr(loc)));
881 });
882
883 // Mark the result as unknown if successful.
884 if (succeeded(result))
885 result->get()->markUnknown();
886
887 return result;
888}
889
890/// Evaluate an unknown value
891FailureOr<evaluator::EvaluatorValuePtr>
892circt::om::Evaluator::evaluateUnknownValue(UnknownValueOp op, Location loc) {
893 return createUnknownValue(op.getType(), loc);
894}
895
896//===----------------------------------------------------------------------===//
897// ObjectValue
898//===----------------------------------------------------------------------===//
899
900/// Get a field of the Object by name.
901FailureOr<EvaluatorValuePtr>
903 auto field = fields.find(name);
904 if (field == fields.end())
905 return cls.emitError("field ") << name << " does not exist";
906 return success(fields[name]);
907}
908
909/// Get an ArrayAttr with the names of the fields in the Object. Sort the fields
910/// so there is always a stable order.
912 SmallVector<Attribute> fieldNames;
913 for (auto &f : fields)
914 fieldNames.push_back(f.first);
915
916 llvm::sort(fieldNames, [](Attribute a, Attribute b) {
917 return cast<StringAttr>(a).getValue() < cast<StringAttr>(b).getValue();
918 });
919
920 return ArrayAttr::get(cls.getContext(), fieldNames);
921}
922
923//===----------------------------------------------------------------------===//
924// BasePathValue
925//===----------------------------------------------------------------------===//
926
928 : EvaluatorValue(context, Kind::BasePath, UnknownLoc::get(context)),
929 path(PathAttr::get(context, {})) {
930 markFullyEvaluated();
931}
932
933evaluator::BasePathValue::BasePathValue(PathAttr path, Location loc)
934 : EvaluatorValue(path.getContext(), Kind::BasePath, loc), path(path) {}
935
937 assert(isFullyEvaluated());
938 return path;
939}
940
942 assert(!isFullyEvaluated());
943 auto newPath = llvm::to_vector(basepath.path.getPath());
944 auto oldPath = path.getPath();
945 newPath.append(oldPath.begin(), oldPath.end());
946 path = PathAttr::get(path.getContext(), newPath);
947 markFullyEvaluated();
948}
949
950//===----------------------------------------------------------------------===//
951// PathValue
952//===----------------------------------------------------------------------===//
953
954evaluator::PathValue::PathValue(TargetKindAttr targetKind, PathAttr path,
955 StringAttr module, StringAttr ref,
956 StringAttr field, Location loc)
957 : EvaluatorValue(loc.getContext(), Kind::Path, loc), targetKind(targetKind),
958 path(path), module(module), ref(ref), field(field) {}
959
961 PathValue path(nullptr, nullptr, nullptr, nullptr, nullptr, loc);
962 path.markFullyEvaluated();
963 return path;
964}
965
967 // If the module is null, then this is a path to a deleted object.
968 if (!targetKind)
969 return StringAttr::get(getContext(), "OMDeleted:");
970 SmallString<64> result;
971 switch (targetKind.getValue()) {
972 case TargetKind::DontTouch:
973 result += "OMDontTouchedReferenceTarget";
974 break;
975 case TargetKind::Instance:
976 result += "OMInstanceTarget";
977 break;
978 case TargetKind::MemberInstance:
979 result += "OMMemberInstanceTarget";
980 break;
981 case TargetKind::MemberReference:
982 result += "OMMemberReferenceTarget";
983 break;
984 case TargetKind::Reference:
985 result += "OMReferenceTarget";
986 break;
987 }
988 result += ":~";
989 if (!path.getPath().empty())
990 result += path.getPath().front().module;
991 else
992 result += module.getValue();
993 result += '|';
994 for (const auto &elt : path) {
995 result += elt.module.getValue();
996 result += '/';
997 result += elt.instance.getValue();
998 result += ':';
999 }
1000 if (!module.getValue().empty())
1001 result += module.getValue();
1002 if (!ref.getValue().empty()) {
1003 result += '>';
1004 result += ref.getValue();
1005 }
1006 if (!field.getValue().empty())
1007 result += field.getValue();
1008 return StringAttr::get(field.getContext(), result);
1009}
1010
1012 assert(!isFullyEvaluated());
1013 auto newPath = llvm::to_vector(basepath.getPath().getPath());
1014 auto oldPath = path.getPath();
1015 newPath.append(oldPath.begin(), oldPath.end());
1016 path = PathAttr::get(path.getContext(), newPath);
1017 markFullyEvaluated();
1018}
1019
1020//===----------------------------------------------------------------------===//
1021// AttributeValue
1022//===----------------------------------------------------------------------===//
1023
1025 if (cast<TypedAttr>(attr).getType() != this->type)
1026 return mlir::emitError(getLoc(), "cannot set AttributeValue of type ")
1027 << this->type << " to Attribute " << attr;
1028 if (isFullyEvaluated())
1029 return mlir::emitError(
1030 getLoc(),
1031 "cannot set AttributeValue that has already been fully evaluated");
1032 this->attr = attr;
1033 markFullyEvaluated();
1034 return success();
1035}
1036
1037std::shared_ptr<evaluator::EvaluatorValue>
1038circt::om::evaluator::AttributeValue::get(Attribute attr, LocationAttr loc) {
1039 auto type = cast<TypedAttr>(attr).getType();
1040 auto *context = type.getContext();
1041 if (!loc)
1042 loc = UnknownLoc::get(context);
1043
1044 // Special handling for ListType to create proper ListValue objects instead of
1045 // AttributeValue objects.
1046 if (auto listType = dyn_cast<circt::om::ListType>(type)) {
1047 SmallVector<EvaluatorValuePtr> elements;
1048 auto listAttr = cast<om::ListAttr>(attr);
1050 listAttr.getContext(), listAttr.getElements().getValue());
1051 elements.append(values.begin(), values.end());
1052 auto list = std::make_shared<evaluator::ListValue>(listType, elements, loc);
1053 return list;
1054 }
1055
1056 return std::shared_ptr<AttributeValue>(
1057 new AttributeValue(PrivateTag{}, attr, loc));
1058}
1059
1060std::shared_ptr<evaluator::EvaluatorValue>
1061circt::om::evaluator::AttributeValue::get(Type type, LocationAttr loc) {
1062 auto *context = type.getContext();
1063 if (!loc)
1064 loc = UnknownLoc::get(context);
1065
1066 // Special handling for ListType to create proper ListValue objects instead of
1067 // AttributeValue objects.
1068 if (auto listType = dyn_cast<circt::om::ListType>(type))
1069 return std::make_shared<evaluator::ListValue>(listType, loc);
1070 // Create the AttributeValue with the private tag
1071 return std::shared_ptr<AttributeValue>(
1072 new AttributeValue(PrivateTag{}, type, loc));
1073}
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.
mlir::ModuleOp getModule()
Get the Module this Evaluator is built from.
ArrayRef< EvaluatorValuePtr > ActualParameters
Definition Evaluator.h:315
FailureOr< evaluator::EvaluatorValuePtr > createUnknownValue(Type type, Location loc)
Create an unknown value of the specified type.
FailureOr< EvaluatorValuePtr > evaluateObjectInstance(StringAttr className, ActualParameters actualParams, Location loc)
Instantiate an Object with its class name and actual parameters.
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)
FailureOr< evaluator::EvaluatorValuePtr > instantiateImpl(StringAttr className, ArrayRef< EvaluatorValuePtr > actualParams)
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.
FailureOr< EvaluatorValuePtr > evaluateParameter(BlockArgument formalParam, ActualParameters actualParams, Location loc)
Evaluator dispatch functions for the small expression grammar.
FailureOr< evaluator::EvaluatorValuePtr > evaluatePathCreate(FrozenPathCreateOp op, ActualParameters actualParams, Location loc)
Values which can be directly representable by MLIR attributes.
Definition Evaluator.h:104
LogicalResult setAttr(Attribute attr)
friend 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:47
A List which contains variadic length of elements with the same type.
Definition Evaluator.h:149
const auto & getElements() const
Definition Evaluator.h:167
om::ListType getListType() const
Return the type of the value, which is a ListType.
Definition Evaluator.h:170
A composite Object, which has a type and fields.
Definition Evaluator.h:183
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:195
StringAttr getAsString() const
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)
std::shared_ptr< EvaluatorValue > EvaluatorValuePtr
A value of an object in memory.
Definition Evaluator.h:37
evaluator::EvaluatorValuePtr EvaluatorValuePtr
Definition Evaluator.h:292
SmallVector< EvaluatorValuePtr > getEvaluatorValuesFromAttributes(MLIRContext *context, ArrayRef< Attribute > attributes)
RAII helper to increment/decrement debugNesting.
Definition Evaluator.h:378