From baf94405f0903ead9d817230c4b505a516dc1a4c Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Thu, 28 May 2026 00:04:40 -0700 Subject: [PATCH 1/5] [OM] Simplify evaluator placeholder handling Remove ReferenceValue indirection and evaluator finalization from the OM evaluator. Object-valued cycles are now represented by preallocated ObjectValue placeholders that are filled once fields are evaluated, so there is no post-evaluation reference-stripping pass. Drop the fully-evaluated counter, worklist, pending-assert flow, parameter-keyed evaluator cache, and recursive ActualParameters threading that existed to resolve ReferenceValue updates. The current instantiation cache is keyed by SSA value, cleared per instantiation, and class parameters are bound once before evaluating the class body. Centralize partial-evaluation checks and use evaluateOp overloads for operation dispatch. Remove the now-obsolete C API and Python bindings for evaluator reference values and update the evaluator tests for the simplified flow. --- include/circt-c/Dialect/OM.h | 9 - .../circt/Dialect/OM/Evaluator/Evaluator.h | 213 +---- lib/Bindings/Python/OMModule.cpp | 4 - lib/CAPI/Dialect/OM.cpp | 25 - lib/Dialect/OM/Evaluator/CMakeLists.txt | 1 - lib/Dialect/OM/Evaluator/Evaluator.cpp | 768 +++--------------- .../Dialect/OM/Evaluator/EvaluatorTests.cpp | 149 ++-- 7 files changed, 168 insertions(+), 1001 deletions(-) diff --git a/include/circt-c/Dialect/OM.h b/include/circt-c/Dialect/OM.h index cb1b148428b3..dd49b5825963 100644 --- a/include/circt-c/Dialect/OM.h +++ b/include/circt-c/Dialect/OM.h @@ -197,15 +197,6 @@ omEvaluatorValueIsAPath(OMEvaluatorValue evaluatorValue); MLIR_CAPI_EXPORTED MlirAttribute omEvaluatorPathGetAsString(OMEvaluatorValue evaluatorValue); -/// Query if the EvaluatorValue is a Reference. -MLIR_CAPI_EXPORTED bool -omEvaluatorValueIsAReference(OMEvaluatorValue evaluatorValue); - -/// Dereference a Reference EvaluatorValue. Emits an error and returns null if -/// the Reference cannot be dereferenced. -MLIR_CAPI_EXPORTED OMEvaluatorValue -omEvaluatorValueGetReferenceValue(OMEvaluatorValue evaluatorValue); - /// Query if the EvaluatorValue is Unknown. MLIR_CAPI_EXPORTED bool omEvaluatorValueIsUnknown(OMEvaluatorValue evaluatorValue); diff --git a/include/circt/Dialect/OM/Evaluator/Evaluator.h b/include/circt/Dialect/OM/Evaluator/Evaluator.h index 3a84c572b153..4de0e46790dd 100644 --- a/include/circt/Dialect/OM/Evaluator/Evaluator.h +++ b/include/circt/Dialect/OM/Evaluator/Evaluator.h @@ -21,11 +21,9 @@ #include "mlir/IR/MLIRContext.h" #include "mlir/IR/SymbolTable.h" #include "mlir/Support/LogicalResult.h" -#include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/Debug.h" -#include #include namespace circt { @@ -49,7 +47,7 @@ using ObjectFields = SmallDenseMap; class EvaluatorValue : public std::enable_shared_from_this { public: // Implement LLVM RTTI. - enum class Kind { Attr, Object, List, Reference, BasePath, Path }; + enum class Kind { Attr, Object, List, BasePath, Path }; EvaluatorValue(MLIRContext *ctx, Kind kind, Location loc) : kind(kind), ctx(ctx), loc(loc) {} Kind getKind() const { return kind; } @@ -61,14 +59,6 @@ class EvaluatorValue : public std::enable_shared_from_this { void markFullyEvaluated() { assert(!fullyEvaluated && "should not mark twice"); fullyEvaluated = true; - // Increment the counter if one is set. - if (fullyEvaluatedCounter) - ++(*fullyEvaluatedCounter); - } - - /// Set a counter to increment when this value becomes fully evaluated. - void setFullyEvaluatedCounter(uint64_t *counter) { - fullyEvaluatedCounter = counter; } /// Return true if the value is unknown (has unknown in its fan-in). @@ -92,9 +82,6 @@ class EvaluatorValue : public std::enable_shared_from_this { // Return a MLIR type which the value represents. Type getType() const; - // Finalize the evaluator value. Strip intermidiate reference values. - LogicalResult finalize(); - // Return the Location associated with the Value. Location getLoc() const { return loc; } // Set the Location associated with the Value. @@ -110,51 +97,7 @@ class EvaluatorValue : public std::enable_shared_from_this { MLIRContext *ctx; Location loc; bool fullyEvaluated = false; - bool finalized = false; bool unknown = false; - uint64_t *fullyEvaluatedCounter = nullptr; -}; - -/// Values which can be used as pointers to different values. -/// ReferenceValue is replaced with its element and erased at the end of -/// evaluation. -class ReferenceValue : public EvaluatorValue { -public: - ReferenceValue(Type type, Location loc) - : EvaluatorValue(type.getContext(), Kind::Reference, loc), value(nullptr), - type(type) {} - - // Implement LLVM RTTI. - static bool classof(const EvaluatorValue *e) { - return e->getKind() == Kind::Reference; - } - - Type getValueType() const { return type; } - EvaluatorValuePtr getValue() const { return value; } - void setValue(EvaluatorValuePtr newValue) { - value = std::move(newValue); - markFullyEvaluated(); - } - - // Finalize the value. - LogicalResult finalizeImpl(); - - // Return the first non-reference value that is reachable from the reference. - FailureOr getStrippedValue() const { - llvm::SmallPtrSet visited; - auto currentValue = value; - while (auto *v = dyn_cast(currentValue.get())) { - // Detect a cycle. - if (!visited.insert(v).second) - return failure(); - currentValue = v->getValue(); - } - return success(currentValue); - } - -private: - EvaluatorValuePtr value; - Type type; }; /// Values which can be directly representable by MLIR attributes. @@ -172,9 +115,6 @@ class AttributeValue : public EvaluatorValue { // Set Attribute for partially evaluated case. LogicalResult setAttr(Attribute attr); - // Finalize the value. - LogicalResult finalizeImpl(); - Type getType() const { return type; } // Factory methods that create AttributeValue objects @@ -205,19 +145,6 @@ class AttributeValue : public EvaluatorValue { friend std::shared_ptr get(Type type, LocationAttr loc); }; -// This perform finalization to `value`. -static inline LogicalResult finalizeEvaluatorValue(EvaluatorValuePtr &value) { - if (failed(value->finalize())) - return failure(); - if (auto *ref = llvm::dyn_cast(value.get())) { - auto v = ref->getStrippedValue(); - if (failed(v)) - return v; - value = v.value(); - } - return success(); -} - /// A List which contains variadic length of elements with the same type. class ListValue : public EvaluatorValue { public: @@ -233,9 +160,6 @@ class ListValue : public EvaluatorValue { markFullyEvaluated(); } - // Finalize the value. - LogicalResult finalizeImpl(); - // Partially evaluated value. ListValue(om::ListType type, Location loc) : EvaluatorValue(type.getContext(), Kind::List, loc), type(type) {} @@ -299,9 +223,6 @@ class ObjectValue : public EvaluatorValue { /// Get all the field names of the Object. ArrayAttr getFieldNames(); - // Finalize the evaluator value. - LogicalResult finalizeImpl(); - private: om::ClassLike cls; llvm::SmallDenseMap fields; @@ -320,9 +241,6 @@ class BasePathValue : public EvaluatorValue { /// Set the basepath which this path is relative to. void setBasepath(const BasePathValue &basepath); - /// Finalize the evaluator value. - LogicalResult finalizeImpl() { return success(); } - /// Implement LLVM RTTI. static bool classof(const EvaluatorValue *e) { return e->getKind() == Kind::BasePath; @@ -355,9 +273,6 @@ class PathValue : public EvaluatorValue { void setBasepath(const BasePathValue &basepath); - // Finalize the evaluator value. - LogicalResult finalizeImpl() { return success(); } - /// Implement LLVM RTTI. static bool classof(const EvaluatorValue *e) { return e->getKind() == Kind::Path; @@ -397,130 +312,43 @@ class Evaluator { FailureOr getPartiallyEvaluatedValue(Type type, Location loc); - using ActualParameters = - SmallVectorImpl> *; - - using ObjectKey = std::pair; - - /// Get the number of fully evaluated nodes tracked by this evaluator. - uint64_t getFullyEvaluatedCount() const { return fullyEvaluatedCount; } - private: - bool isFullyEvaluated(Value value, ActualParameters key) { - return isFullyEvaluated({value, key}); - } - - bool isFullyEvaluated(ObjectKey key) { - auto val = objects.lookup(key); - return val && val->isFullyEvaluated(); - } - - /// Attach the evaluation counter to a newly created value. - void attachCounter(evaluator::EvaluatorValuePtr &value) { - if (value && !value->isFullyEvaluated()) - value->setFullyEvaluatedCounter(&fullyEvaluatedCount); - } - FailureOr instantiateImpl(StringAttr className, ArrayRef actualParams); - FailureOr - getOrCreateValue(Value value, ActualParameters actualParams, Location loc); - FailureOr - allocateObjectInstance(StringAttr clasName, ActualParameters actualParams); + FailureOr getOrCreateValue(Value value, Location loc); /// Evaluate a Value in a Class body according to the small expression grammar - /// described in the rationale document. The actual parameters are the values - /// supplied at the current instantiation of the Class being evaluated. - FailureOr - evaluateValue(Value value, ActualParameters actualParams, Location loc); + /// described in the rationale document. + FailureOr evaluateValue(Value value, Location loc); - /// Evaluator dispatch functions for the small expression grammar. - FailureOr evaluateParameter(BlockArgument formalParam, - ActualParameters actualParams, - Location loc); - - FailureOr - evaluateConstant(ConstantOp op, ActualParameters actualParams, Location loc); - - /// Instantiate an Object with its class name and actual parameters. - FailureOr - evaluateObjectInstance(StringAttr className, ActualParameters actualParams, - Location loc, ObjectKey instanceKey = {}); - FailureOr - evaluateObjectInstance(ObjectOp op, ActualParameters actualParams); + /// Evaluate a class body with actual parameters. FailureOr - evaluateElaboratedObject(ElaboratedObjectOp op, ActualParameters actualParams, - Location loc); - FailureOr - evaluateObjectField(ObjectFieldOp op, ActualParameters actualParams, - Location loc); - FailureOr evaluateListCreate(ListCreateOp op, - ActualParameters actualParams, - Location loc); - FailureOr evaluateListConcat(ListConcatOp op, - ActualParameters actualParams, - Location loc); - FailureOr - evaluateIntegerBinary(IntegerBinaryOp op, ActualParameters actualParams, - Location loc); - FailureOr - evaluateStringConcat(StringConcatOp op, ActualParameters actualParams, - Location loc); - FailureOr - evaluateBinaryEquality(BinaryEqualityOp op, ActualParameters actualParams, + evaluateObjectInstance(StringAttr className, + ArrayRef actualParams, Location loc); - FailureOr - evaluateBasePathCreate(FrozenBasePathCreateOp op, - ActualParameters actualParams, Location loc); - FailureOr - evaluatePathCreate(FrozenPathCreateOp op, ActualParameters actualParams, - Location loc); - FailureOr - evaluateEmptyPath(FrozenEmptyPathOp op, ActualParameters actualParams, - Location loc); - FailureOr - evaluateUnknownValue(UnknownValueOp op, Location loc); - LogicalResult evaluatePropertyAssert(PropertyAssertOp op, - ActualParameters actualParams); + /// Evaluator dispatch functions for the small expression grammar. + FailureOr evaluateOp(ConstantOp op, Location loc); + FailureOr evaluateOp(ElaboratedObjectOp op, Location loc); + FailureOr evaluateOp(ListCreateOp op, Location loc); + FailureOr evaluateOp(ListConcatOp op, Location loc); + FailureOr evaluateOp(FrozenBasePathCreateOp op, + Location loc); + FailureOr evaluateOp(FrozenPathCreateOp op, Location loc); + FailureOr evaluateOp(FrozenEmptyPathOp op, Location loc); + FailureOr evaluateOp(UnknownValueOp op, Location loc); FailureOr createUnknownValue(Type type, Location loc); - FailureOr - createParametersFromOperands(ValueRange range, ActualParameters actualParams, - Location loc); - /// The symbol table for the IR module the Evaluator was constructed with. /// Used to look up class definitions. SymbolTable symbolTable; - /// This uniquely stores vectors that represent parameters. - SmallVector< - std::unique_ptr>>> - actualParametersBuffers; - - /// Worklists that track values which need to be fully evaluated. - /// We use two worklists to detect cycles: process all items from one, - /// and if any become fully evaluated, swap and continue. - std::vector worklist; - std::vector nextWorklist; - - /// A queue of pending property assertions to be evaluated after the worklist - /// is fully drained. Each entry is a (PropertyAssertOp, ActualParameters) - /// pair. Property assertions are deferred because their operands may be - /// ReferenceValues that are not yet resolved when the class body is first - /// processed. - std::queue> pendingAsserts; - - /// Evaluator value storage. Return an evaluator value for the given - /// instantiation context (a pair of Value and parameters). - DenseMap> objects; - - /// Counter for fully evaluated nodes. - uint64_t fullyEvaluatedCount = 0; + /// Evaluator value storage for the current instantiation. + DenseMap> objects; #ifndef NDEBUG /// Current nesting depth for debug output indentation. @@ -554,9 +382,6 @@ operator<<(mlir::Diagnostic &diag, diag << "Object(" << object->getType() << ")"; else if (auto *list = llvm::dyn_cast(&evaluatorValue)) diag << "List(" << list->getType() << ")"; - else if (auto *ref = - llvm::dyn_cast(&evaluatorValue)) - diag << "Reference(" << ref->getValueType() << ")"; else if (llvm::isa(&evaluatorValue)) diag << "BasePath()"; else if (llvm::isa(&evaluatorValue)) diff --git a/lib/Bindings/Python/OMModule.cpp b/lib/Bindings/Python/OMModule.cpp index 489a46e66b64..d0955dbcafc0 100644 --- a/lib/Bindings/Python/OMModule.cpp +++ b/lib/Bindings/Python/OMModule.cpp @@ -376,10 +376,6 @@ PythonValue omEvaluatorValueToPythonValue(OMEvaluatorValue result) { if (omEvaluatorValueIsUnknown(result)) return Unknown(omEvaluatorValueGetType(result)); - if (omEvaluatorValueIsAReference(result)) - return omEvaluatorValueToPythonValue( - omEvaluatorValueGetReferenceValue(result)); - // If the field was a primitive, return the Attribute. assert(omEvaluatorValueIsAPrimitive(result)); return omPrimitiveToPythonValue(omEvaluatorValueGetPrimitive(result)); diff --git a/lib/CAPI/Dialect/OM.cpp b/lib/CAPI/Dialect/OM.cpp index 9364d2535115..0c8ed8ff07b6 100644 --- a/lib/CAPI/Dialect/OM.cpp +++ b/lib/CAPI/Dialect/OM.cpp @@ -296,31 +296,6 @@ MlirAttribute omEvaluatorPathGetAsString(OMEvaluatorValue evaluatorValue) { return wrap((Attribute)path->getAsString()); } -/// Query if the EvaluatorValue is a Reference. -bool omEvaluatorValueIsAReference(OMEvaluatorValue evaluatorValue) { - return isa(unwrap(evaluatorValue).get()); -} - -/// Dereference a Reference EvaluatorValue. Emits an error and returns null if -/// the Reference cannot be dereferenced. -OMEvaluatorValue -omEvaluatorValueGetReferenceValue(OMEvaluatorValue evaluatorValue) { - // Assert the EvaluatorValue is a Reference. - assert(omEvaluatorValueIsAReference(evaluatorValue)); - - // Attempt to get the final EvaluatorValue from the Reference. - auto result = - llvm::cast(unwrap(evaluatorValue).get()) - ->getStrippedValue(); - - // If this failed, an error diagnostic has been emitted, and we return null. - if (failed(result)) - return {}; - - // If this succeeded, wrap the EvaluatorValue and return it. - return wrap(result.value()); -} - /// Query if the EvaluatorValue is an Unknown value. bool omEvaluatorValueIsUnknown(OMEvaluatorValue evaluatorValue) { return unwrap(evaluatorValue)->isUnknown(); diff --git a/lib/Dialect/OM/Evaluator/CMakeLists.txt b/lib/Dialect/OM/Evaluator/CMakeLists.txt index 6576782ba383..a833a29e2f5f 100644 --- a/lib/Dialect/OM/Evaluator/CMakeLists.txt +++ b/lib/Dialect/OM/Evaluator/CMakeLists.txt @@ -4,7 +4,6 @@ add_circt_library(CIRCTOMEvaluator DEPENDS MLIROMIncGen MLIROMAttrIncGen - MLIROMOpInterfacesIncGen LINK_LIBS CIRCTOM diff --git a/lib/Dialect/OM/Evaluator/Evaluator.cpp b/lib/Dialect/OM/Evaluator/Evaluator.cpp index 9f635baa7c69..d838a5e9ad50 100644 --- a/lib/Dialect/OM/Evaluator/Evaluator.cpp +++ b/lib/Dialect/OM/Evaluator/Evaluator.cpp @@ -31,9 +31,6 @@ using namespace circt::om; namespace { -constexpr StringLiteral skipElaborationTransformAttr = - "om.skip_elaboration_transform"; - LogicalResult verifyActualParameters(ClassLike classLike, ArrayRef actualParams) { auto formalParamNames = @@ -82,6 +79,11 @@ LogicalResult verifyActualParameters(ClassLike classLike, return success(); } +bool requiresCompleteEvaluation(const evaluator::EvaluatorValuePtr &value) { + return !value->isFullyEvaluated() && + !isa(value.get()); +} + /// A helper class that builds the scratch IR for evaluating an object. This is /// used to convert from the evaluator's API (which uses opaque pointers to /// evaluator values) into actual MLIR IR. @@ -104,7 +106,7 @@ class ScratchIRBuilder { ClassOp createWrapperClass(ClassLike rootClass); /// Convert an API input value into scratch IR, preserving opaque any-typed - /// inputs and rejecting runtime references/cycles. + /// inputs and rejecting object cycles. FailureOr materializeInput(const EvaluatorValuePtr &value, Location loc, Type expectedType); /// Convert a fully evaluated list value into scratch IR. @@ -200,8 +202,6 @@ ScratchIRBuilder::materializeInput(const EvaluatorValuePtr &value, Location loc, return emitError(loc, "cannot materialize null OM evaluator value"); loc = value->getLoc(); - if (isa(value.get())) - return emitError(loc, "cannot import OM reference value"); if (!expectedType) return emitError(loc, "cannot import OM evaluator value without an " "expected type"); @@ -336,25 +336,11 @@ circt::om::getEvaluatorValuesFromAttributes(MLIRContext *context, return values; } -LogicalResult circt::om::evaluator::EvaluatorValue::finalize() { - using namespace evaluator; - // Early return if already finalized. - if (finalized) - return success(); - // Enable the flag to avoid infinite recursions. - finalized = true; - assert(isFullyEvaluated()); - return llvm::TypeSwitch(this) - .Case([](auto v) { return v->finalizeImpl(); }); -} - Type circt::om::evaluator::EvaluatorValue::getType() const { return llvm::TypeSwitch(this) .Case([](auto *attr) -> Type { return attr->getType(); }) .Case([](auto *object) { return object->getObjectType(); }) .Case([](auto *list) { return list->getListType(); }) - .Case([](auto *ref) { return ref->getValueType(); }) .Case( [this](auto *tuple) { return FrozenBasePathType::get(ctx); }) .Case( @@ -393,17 +379,14 @@ circt::om::Evaluator::getPartiallyEvaluatedValue(Type type, Location loc) { }) .Default([&](auto type) { return failure(); }); - if (succeeded(result)) - attachCounter(result.value()); - return result; } -FailureOr circt::om::Evaluator::getOrCreateValue( - Value value, ActualParameters actualParams, Location loc) { +FailureOr +circt::om::Evaluator::getOrCreateValue(Value value, Location loc) { LLVM_DEBUG(dbgs() << "- get: " << value << "\n"); - auto it = objects.find({value, actualParams}); + auto it = objects.find(value); if (it != objects.end()) { auto evalVal = it->second; evalVal->setLocIfUnknown(loc); @@ -413,35 +396,19 @@ FailureOr circt::om::Evaluator::getOrCreateValue( FailureOr result = TypeSwitch>(value) .Case([&](BlockArgument arg) { - auto val = (*actualParams)[arg.getArgNumber()]; - val->setLoc(loc); - return val; + auto error = arg.getOwner()->getParentOp()->emitError( + "unable to evaluate unbound parameter"); + error.attachNote() << "value: " << value; + return error; }) .Case([&](OpResult result) { return TypeSwitch>( result.getDefiningOp()) - .Case([&](ConstantOp op) { - return evaluateConstant(op, actualParams, loc); - }) - .Case([&](IntegerBinaryOp op) { - // Create a partially evaluated AttributeValue in case we need - // to delay evaluation. - evaluator::EvaluatorValuePtr result = - evaluator::AttributeValue::get(op.getResult().getType(), - loc); - return success(result); - }) - .Case([&](auto op) { - // Create a reference value since the value pointed by object - // field op is not created yet. - evaluator::EvaluatorValuePtr result = - std::make_shared( - value.getType(), loc); - return success(result); - }) + .Case( + [&](auto op) { return evaluateOp(op, loc); }) .Case([&](AnyCastOp op) { - return getOrCreateValue(op.getInput(), actualParams, loc); + return getOrCreateValue(op.getInput(), loc); }) .Case([&](FrozenBasePathCreateOp op) { evaluator::EvaluatorValuePtr result = @@ -463,24 +430,12 @@ FailureOr circt::om::Evaluator::getOrCreateValue( evaluator::PathValue::getEmptyPath(loc)); return success(result); }) - .Case([&](BinaryEqualityOp op) { - evaluator::EvaluatorValuePtr result = - evaluator::AttributeValue::get(op.getResult().getType(), - loc); - return success(result); - }) - .Case([&](auto op) { + .Case([&](auto op) { return getPartiallyEvaluatedValue(op.getType(), loc); }) - .Case([&](auto op) { - return getPartiallyEvaluatedValue(op.getType(), op.getLoc()); - }) .Case([&](auto op) { return getPartiallyEvaluatedValue(op.getType(), op.getLoc()); }) - .Case( - [&](auto op) { return evaluateUnknownValue(op, loc); }) .Default([&](Operation *op) { auto error = op->emitError("unable to evaluate value"); error.attachNote() << "value: " << value; @@ -490,17 +445,14 @@ FailureOr circt::om::Evaluator::getOrCreateValue( if (failed(result)) return result; - // Attach listener to newly created values - attachCounter(result.value()); - objects[{value, actualParams}] = result.value(); + objects[value] = result.value(); return result; } FailureOr -circt::om::Evaluator::evaluateObjectInstance(StringAttr className, - ActualParameters actualParams, - Location loc, - ObjectKey instanceKey) { +circt::om::Evaluator::evaluateObjectInstance( + StringAttr className, ArrayRef actualParams, + Location loc) { #ifndef NDEBUG DebugNesting nestOne(debugNesting); #endif @@ -518,7 +470,6 @@ circt::om::Evaluator::evaluateObjectInstance(StringAttr className, if (isa(classDef)) { evaluator::EvaluatorValuePtr result = std::make_shared(classDef, loc); - attachCounter(result); result->markUnknown(); LLVM_DEBUG(dbgs(1) << "extern: \n"); return result; @@ -527,28 +478,41 @@ circt::om::Evaluator::evaluateObjectInstance(StringAttr className, // Otherwise, it's a regular class, proceed normally ClassOp cls = cast(classDef); - if (failed(verifyActualParameters(cls, *actualParams))) + if (failed(verifyActualParameters(cls, actualParams))) return failure(); - // Instantiate the fields. - evaluator::ObjectFields fields; + for (auto [arg, actual] : + llvm::zip(cls.getBodyBlock()->getArguments(), actualParams)) { + actual->setLoc(arg.getLoc()); + objects.try_emplace(arg, actual); + } + evaluator::ObjectFields fields; auto *context = cls.getContext(); { LLVM_DEBUG(dbgs() << "ops:\n"); #ifndef NDEBUG DebugNesting nestOne(debugNesting); #endif + // Allocate placeholders for all class-body results before evaluating any + // fields. for (auto &op : cls.getOps()) + for (auto result : op.getResults()) + if (failed(getOrCreateValue(result, UnknownLoc::get(context)))) + return failure(); + + // A later field evaluation can then connect object valued cycles by + // pointing at these placeholders, and the placeholders are filled + // below. + for (auto &op : cls.getOps()) { for (auto result : op.getResults()) { - // Allocate the value, with unknown loc. It will be later set when - // evaluating the fields. - if (failed(getOrCreateValue(result, actualParams, - UnknownLoc::get(context)))) + auto evaluated = evaluateValue(result, op.getLoc()); + if (failed(evaluated)) return failure(); - // Add to the worklist. - worklist.push_back({result, actualParams}); + if (requiresCompleteEvaluation(evaluated.value())) + return op.emitError("failed to evaluate value"); } + } } LLVM_DEBUG(dbgs() << "fields:\n"); @@ -564,36 +528,18 @@ circt::om::Evaluator::evaluateObjectInstance(StringAttr className, DebugNesting nestOne(debugNesting); #endif FailureOr result = - evaluateValue(value, actualParams, fieldLoc); + evaluateValue(value, fieldLoc); if (failed(result)) return result; + if (requiresCompleteEvaluation(result.value())) + return emitError(fieldLoc, "failed to evaluate field ") << name; LLVM_DEBUG(dbgs() << "value: " << result.value() << "\n"); fields[cast(name)] = result.value(); } - // Defer property assertions until after the worklist is drained, so that - // all ReferenceValues are fully resolved before we try to inspect them. - LLVM_DEBUG(dbgs() << "queuing asserts:\n"); - for (auto assertOp : cls.getOps()) { - LLVM_DEBUG(dbgs(1) << "- " << assertOp << "\n"); - pendingAsserts.push({assertOp, actualParams}); - } - - // If the there is an instance, we must update the object value. - LLVM_DEBUG(dbgs() << "object value:\n"); - if (instanceKey.first) { - auto result = - getOrCreateValue(instanceKey.first, instanceKey.second, loc).value(); - auto *object = llvm::cast(result.get()); - object->setFields(std::move(fields)); - return result; - } - - // If it's external call, just allocate new ObjectValue. evaluator::EvaluatorValuePtr result = std::make_shared(cls, fields, loc); - // Object is already fully evaluated when created with fields. assert(result->isFullyEvaluated() && "object with fields should be fully evaluated"); return result; @@ -613,13 +559,6 @@ circt::om::Evaluator::instantiate( dbgs() << "- " << param << "\n"; }); - // Skip the elaboration transform and directly instantiate the class if the - // caller explicitly requests so. - // TODO: Remove this after fully migrating to the new evaluator-based - // implementation. - if (getModule()->hasAttr(skipElaborationTransformAttr)) - return instantiateImpl(className, actualParams); - auto rootClass = symbolTable.lookup(className); if (!rootClass) return symbolTable.getOp()->emitError("unknown class name ") << className; @@ -655,7 +594,6 @@ circt::om::Evaluator::instantiateImpl( evaluator::EvaluatorValuePtr result = std::make_shared( classDef, UnknownLoc::get(classDef.getContext())); - attachCounter(result); result->markUnknown(); LLVM_DEBUG(dbgs(1) << "result: \n"); return result; @@ -664,150 +602,44 @@ circt::om::Evaluator::instantiateImpl( // Otherwise, it's a regular class, proceed normally ClassOp cls = cast(classDef); - auto parameters = - std::make_unique>>( - actualParams); - - actualParametersBuffers.push_back(std::move(parameters)); + objects.clear(); auto loc = cls.getLoc(); LLVM_DEBUG(dbgs() << "evaluate object:\n"); - auto result = evaluateObjectInstance( - className, actualParametersBuffers.back().get(), loc); + auto result = evaluateObjectInstance(className, actualParams, loc); if (failed(result)) return failure(); - // `evaluateObjectInstance` has populated the worklist. Continue evaluations - // unless there is a partially evaluated value. - LLVM_DEBUG(dbgs() << "worklist:\n"); - - // Use two-worklist approach: process all items from current worklist, and if - // at least one becomes fully evaluated, swap and continue. If a full pass - // completes with no progress, we have a cycle. - while (!worklist.empty()) { - uint64_t countBeforePass = fullyEvaluatedCount; - LLVM_DEBUG(dbgs() << "- processing " << worklist.size() - << " items (fully evaluated count: " - << fullyEvaluatedCount << ")\n"); - - // Process all items in the current worklist. - while (!worklist.empty()) { - auto [value, args] = worklist.back(); - worklist.pop_back(); - auto result = evaluateValue(value, args, loc); - - if (failed(result)) - return failure(); - - // If not fully evaluated, add to next worklist for retry. - if (!result.value()->isFullyEvaluated()) - nextWorklist.push_back({value, args}); - } - - // Check if we made progress. - uint64_t evaluatedThisPass = fullyEvaluatedCount - countBeforePass; - LLVM_DEBUG(dbgs() << "- evaluated " << evaluatedThisPass - << " nodes this pass\n"); - - // If nothing became fully evaluated in this pass, we have a cycle. - if (evaluatedThisPass == 0 && !nextWorklist.empty()) - return cls.emitError() - << "cycle detected: " << nextWorklist.size() - << " values remain partially evaluated after full pass with no " - "progress (total fully evaluated: " - << fullyEvaluatedCount << ")"; - - // Swap worklists for next iteration. - worklist = std::move(nextWorklist); - nextWorklist.clear(); - } - - // Now that all values are fully resolved, evaluate the deferred property - // assertions. - LLVM_DEBUG(dbgs() << "asserts:\n"); - bool assertFailed = false; - while (!pendingAsserts.empty()) { - auto [assertOp, assertParams] = pendingAsserts.front(); - pendingAsserts.pop(); - assertFailed |= failed(evaluatePropertyAssert(assertOp, assertParams)); - } - if (assertFailed) - return failure(); - - auto &object = result.value(); - // Finalize the value. This will eliminate intermidiate ReferenceValue used as - // a placeholder in the initialization. - LLVM_DEBUG(dbgs() << "finalizing\n"); - if (failed(object->finalize())) - return cls.emitError() << "failed to finalize evaluation. Probably the " - "class contains a dataflow cycle"; - LLVM_DEBUG(dbgs() << "result: " << object << "\n"); - return object; + LLVM_DEBUG(dbgs() << "result: " << result.value() << "\n"); + return result; } FailureOr -circt::om::Evaluator::evaluateValue(Value value, ActualParameters actualParams, - Location loc) { - auto evaluatorValue = getOrCreateValue(value, actualParams, loc).value(); +circt::om::Evaluator::evaluateValue(Value value, Location loc) { + auto evaluatorValue = getOrCreateValue(value, loc); + if (failed(evaluatorValue)) + return failure(); LLVM_DEBUG(dbgs() << "- eval: " << value << "\n"); // Return if the value is already evaluated. - if (evaluatorValue->isFullyEvaluated()) { - LLVM_DEBUG(dbgs(1) << "fully evaluated: " << evaluatorValue << "\n"); + if (evaluatorValue.value()->isFullyEvaluated()) { + LLVM_DEBUG(dbgs(1) << "fully evaluated: " << evaluatorValue.value() + << "\n"); return evaluatorValue; } return llvm::TypeSwitch>(value) - .Case([&](BlockArgument arg) { - return evaluateParameter(arg, actualParams, loc); - }) + .Case([&](BlockArgument arg) { return evaluatorValue; }) .Case([&](OpResult result) { return TypeSwitch>( result.getDefiningOp()) - .Case([&](ConstantOp op) { - return evaluateConstant(op, actualParams, loc); - }) - .Case([&](IntegerBinaryOp op) { - return evaluateIntegerBinary(op, actualParams, loc); - }) - .Case([&](ObjectOp op) { - return evaluateObjectInstance(op, actualParams); - }) - .Case([&](ElaboratedObjectOp op) { - return evaluateElaboratedObject(op, actualParams, loc); - }) - .Case([&](ObjectFieldOp op) { - return evaluateObjectField(op, actualParams, loc); - }) - .Case([&](ListCreateOp op) { - return evaluateListCreate(op, actualParams, loc); - }) - .Case([&](ListConcatOp op) { - return evaluateListConcat(op, actualParams, loc); - }) - .Case([&](StringConcatOp op) { - return evaluateStringConcat(op, actualParams, loc); - }) - .Case([&](BinaryEqualityOp op) { - return evaluateBinaryEquality(op, actualParams, loc); - }) - .Case([&](AnyCastOp op) { - return evaluateValue(op.getInput(), actualParams, loc); - }) - .Case([&](FrozenBasePathCreateOp op) { - return evaluateBasePathCreate(op, actualParams, loc); - }) - .Case([&](FrozenPathCreateOp op) { - return evaluatePathCreate(op, actualParams, loc); - }) - .Case([&](FrozenEmptyPathOp op) { - return evaluateEmptyPath(op, actualParams, loc); - }) - .Case([&](UnknownValueOp op) { - return evaluateUnknownValue(op, loc); - }) + .Case([&](auto op) { return evaluateOp(op, loc); }) + .Case( + [&](AnyCastOp op) { return evaluateValue(op.getInput(), loc); }) .Default([&](Operation *op) { auto error = op->emitError("unable to evaluate value"); error.attachNote() << "value: " << value; @@ -816,195 +648,16 @@ circt::om::Evaluator::evaluateValue(Value value, ActualParameters actualParams, }); } -/// Evaluator dispatch function for parameters. -FailureOr circt::om::Evaluator::evaluateParameter( - BlockArgument formalParam, ActualParameters actualParams, Location loc) { - auto val = (*actualParams)[formalParam.getArgNumber()]; - val->setLoc(loc); - return success(val); -} - /// Evaluator dispatch function for constants. FailureOr -circt::om::Evaluator::evaluateConstant(ConstantOp op, - ActualParameters actualParams, - Location loc) { +circt::om::Evaluator::evaluateOp(ConstantOp op, Location loc) { // For list constants, create ListValue. return success(om::evaluator::AttributeValue::get(op.getValue(), loc)); } -// Evaluator dispatch function for integer binary operations. -FailureOr circt::om::Evaluator::evaluateIntegerBinary( - IntegerBinaryOp op, ActualParameters actualParams, Location loc) { - // Get the op's EvaluatorValue handle, in case it hasn't been evaluated yet. - auto handle = getOrCreateValue(op.getResult(), actualParams, loc); - - // If it's fully evaluated, we can return it. - if (handle.value()->isFullyEvaluated()) - return handle; - - // Evaluate operands if necessary, and return the partially evaluated value if - // they aren't ready. - auto lhsResult = evaluateValue(op.getLhs(), actualParams, loc); - if (failed(lhsResult)) - return lhsResult; - if (!lhsResult.value()->isFullyEvaluated()) - return handle; - - auto rhsResult = evaluateValue(op.getRhs(), actualParams, loc); - if (failed(rhsResult)) - return rhsResult; - if (!rhsResult.value()->isFullyEvaluated()) - return handle; - - // Check if any operand is unknown and propagate the unknown flag. - if (lhsResult.value()->isUnknown() || rhsResult.value()->isUnknown()) { - handle.value()->markUnknown(); - return handle; - } - - // Extract the attribute from an EvaluatorValue (handles both om::IntegerAttr - // and mlir::IntegerAttr). - auto extractAttr = [](evaluator::EvaluatorValue *value) -> Attribute { - return llvm::TypeSwitch(value) - .Case([](evaluator::AttributeValue *val) { return val->getAttr(); }) - .Case([](evaluator::ReferenceValue *val) { - return cast(val->getStrippedValue()->get()) - ->getAttr(); - }); - }; - - mlir::Attribute lhsAttr = extractAttr(lhsResult.value().get()); - mlir::Attribute rhsAttr = extractAttr(rhsResult.value().get()); - assert(lhsAttr && rhsAttr && - "expected attribute for IntegerBinaryOp operands"); - - std::array operandAttrs = {lhsAttr, rhsAttr}; - SmallVector results; - mlir::Attribute resultAttr; - // Even with fully constant operands, folders may decline to fold or may - // produce a non-attribute result. - if (failed(op->fold(operandAttrs, results)) || results.size() != 1 || - !(resultAttr = results[0].dyn_cast())) - return op->emitError("failed to evaluate integer operation"); - - // Finalize the op result value. - auto *handleValue = cast(handle.value().get()); - auto resultStatus = handleValue->setAttr(resultAttr); - if (failed(resultStatus)) - return resultStatus; - - auto finalizeStatus = handleValue->finalize(); - if (failed(finalizeStatus)) - return finalizeStatus; - - return handle; -} - -/// Evaluator dispatch function for property assertions. -LogicalResult -circt::om::Evaluator::evaluatePropertyAssert(PropertyAssertOp op, - ActualParameters actualParams) { -#ifndef NDEBUG - DebugNesting nest(debugNesting); -#endif - - auto loc = op.getLoc(); - - // Evaluate the condition, returning early if it isn't ready yet. - LLVM_DEBUG(dbgs() << "op: " << op << "\n" - << indent() << "evaluate condition: \n"); - auto condResult = evaluateValue(op.getCondition(), actualParams, loc); - if (failed(condResult)) - return failure(); - if (!condResult.value()->isFullyEvaluated()) { - LLVM_DEBUG(dbgs() << "evaluate condition: \n"); - return success(); - } - - // If the condition is unknown, skip silently (best-effort). - if (condResult.value()->isUnknown()) - return success(); - - LLVM_DEBUG(dbgs() << "condition: " << condResult.value() << "\n"); - - // Extract the attribute from the condition value, handling the case where - // the condition resolves through a ReferenceValue (e.g. an ObjectFieldOp or - // a parameter that participates in cycle resolution). - auto extractAttr = [](evaluator::EvaluatorValue *value) -> mlir::Attribute { - return llvm::TypeSwitch(value) - .Case([](evaluator::AttributeValue *val) { return val->getAttr(); }) - .Case([](evaluator::ReferenceValue *val) -> mlir::Attribute { - auto stripped = val->getStrippedValue(); - if (failed(stripped)) - return {}; - if (auto *attr = - dyn_cast(stripped.value().get())) - return attr->getAttr(); - return {}; - }) - .Default([](auto *) -> mlir::Attribute { return {}; }); - }; - - auto condAttr = extractAttr(condResult.value().get()); - if (!condAttr) - return success(); - - bool isFalse = false; - if (auto boolAttr = dyn_cast(condAttr)) - isFalse = !boolAttr.getValue(); - else if (auto intAttr = dyn_cast(condAttr)) - isFalse = intAttr.getValue().isZero(); - else - return op.emitError("expected BoolAttr or mlir::IntegerAttr"); - - if (isFalse) - return op.emitError("OM property assertion failed: ") << op.getMessage(); - - return success(); -} - -/// Evaluator dispatch function for Object instances. -FailureOr -circt::om::Evaluator::createParametersFromOperands( - ValueRange range, ActualParameters actualParams, Location loc) { - // Create an unique storage to store parameters. - auto parameters = std::make_unique< - SmallVector>>(); - - // Collect operands' evaluator values in the current instantiation context. - for (auto input : range) { - auto inputResult = getOrCreateValue(input, actualParams, loc); - if (failed(inputResult)) - return failure(); - parameters->push_back(inputResult.value()); - } - - actualParametersBuffers.push_back(std::move(parameters)); - return actualParametersBuffers.back().get(); -} - -/// Evaluator dispatch function for Object instances. FailureOr -circt::om::Evaluator::evaluateObjectInstance(ObjectOp op, - ActualParameters actualParams) { - auto loc = op.getLoc(); - if (isFullyEvaluated({op, actualParams})) - return getOrCreateValue(op, actualParams, loc); - - auto params = - createParametersFromOperands(op.getOperands(), actualParams, loc); - if (failed(params)) - return failure(); - return evaluateObjectInstance(op.getClassNameAttr().getAttr(), params.value(), - loc, {op, actualParams}); -} - -FailureOr -circt::om::Evaluator::evaluateElaboratedObject(ElaboratedObjectOp op, - ActualParameters actualParams, - Location loc) { - auto objectValue = getOrCreateValue(op, actualParams, loc); +circt::om::Evaluator::evaluateOp(ElaboratedObjectOp op, Location loc) { + auto objectValue = getOrCreateValue(op, loc); if (failed(objectValue)) return failure(); auto object = cast(objectValue.value().get()); @@ -1030,12 +683,16 @@ circt::om::Evaluator::evaluateElaboratedObject(ElaboratedObjectOp op, llvm::enumerate(llvm::zip(fieldNames, fieldValues))) { auto [fieldName, fieldValue] = fieldNameAndValue; auto fieldLoc = classOp ? classOp.getFieldLocByIndex(index) : loc; - auto fieldResult = getOrCreateValue(fieldValue, actualParams, fieldLoc); + auto fieldResult = getOrCreateValue(fieldValue, fieldLoc); + if (failed(fieldResult)) + return failure(); + if (!isa(fieldResult.value().get())) + fieldResult = evaluateValue(fieldValue, fieldLoc); if (failed(fieldResult)) return failure(); - if (!fieldResult.value()->isFullyEvaluated()) - worklist.push_back({fieldValue, actualParams}); + if (requiresCompleteEvaluation(fieldResult.value())) + return emitError(fieldLoc, "failed to evaluate field ") << fieldName; fields[cast(fieldName)] = fieldResult.value(); } @@ -1044,81 +701,20 @@ circt::om::Evaluator::evaluateElaboratedObject(ElaboratedObjectOp op, return objectValue; } -/// Evaluator dispatch function for Object fields. -FailureOr -circt::om::Evaluator::evaluateObjectField(ObjectFieldOp op, - ActualParameters actualParams, - Location loc) { - // Evaluate the Object itself, in case it hasn't been evaluated yet. - FailureOr currentObjectResult = - evaluateValue(op.getObject(), actualParams, loc); - if (failed(currentObjectResult)) - return currentObjectResult; - - auto result = currentObjectResult.value(); - - auto objectFieldValue = getOrCreateValue(op, actualParams, loc).value(); - - if (result->isUnknown()) { - // If objectFieldValue is a ReferenceValue, set its value to a unknown value - // of the proper type - if (auto *ref = - llvm::dyn_cast(objectFieldValue.get())) { - auto unknownField = createUnknownValue(op.getResult().getType(), loc); - if (failed(unknownField)) - return unknownField; - ref->setValue(unknownField.value()); - } - // markUnknown() also marks the value as fully evaluated - objectFieldValue->markUnknown(); - return objectFieldValue; - } - - // If the result is a ReferenceValue, dereference it to get the actual object. - if (auto *ref = llvm::dyn_cast(result.get())) { - auto stripped = ref->getStrippedValue(); - if (failed(stripped)) - return failure(); - result = stripped.value(); - } - - auto *currentObject = llvm::cast(result.get()); - - auto field = op.getFieldAttr(); - - // `currentObject` might not be fully evaluated. - if (!currentObject->getFields().contains(field)) - return objectFieldValue; - - auto currentField = currentObject->getField(field); - auto finalField = currentField.value(); - - if (!finalField->isFullyEvaluated()) - return objectFieldValue; - - // Update the reference. - llvm::cast(objectFieldValue.get()) - ->setValue(finalField); - - // Return the field being accessed. - return objectFieldValue; -} - /// Evaluator dispatch function for List creation. FailureOr -circt::om::Evaluator::evaluateListCreate(ListCreateOp op, - ActualParameters actualParams, - Location loc) { +circt::om::Evaluator::evaluateOp(ListCreateOp op, Location loc) { // Evaluate the Object itself, in case it hasn't been evaluated yet. SmallVector values; - auto list = getOrCreateValue(op, actualParams, loc); + auto list = getOrCreateValue(op, loc); bool hasUnknown = false; for (auto operand : op.getOperands()) { - auto result = evaluateValue(operand, actualParams, loc); + auto result = evaluateValue(operand, loc); if (failed(result)) return result; - if (!result.value()->isFullyEvaluated()) - return list; + if (requiresCompleteEvaluation(result.value())) + return op.emitError() + << "failed to evaluate list element operand: " << operand; // Check if any operand is unknown. if (result.value()->isUnknown()) hasUnknown = true; @@ -1140,27 +736,22 @@ circt::om::Evaluator::evaluateListCreate(ListCreateOp op, /// Evaluator dispatch function for List concatenation. FailureOr -circt::om::Evaluator::evaluateListConcat(ListConcatOp op, - ActualParameters actualParams, - Location loc) { +circt::om::Evaluator::evaluateOp(ListConcatOp op, Location loc) { // Evaluate the List concat op itself, in case it hasn't been evaluated yet. SmallVector values; - auto list = getOrCreateValue(op, actualParams, loc); + auto list = getOrCreateValue(op, loc); - // Extract the ListValue, either directly or through an object reference. + // Extract the ListValue. auto extractList = [](evaluator::EvaluatorValue *value) { return std::move( llvm::TypeSwitch( value) - .Case([](evaluator::ListValue *val) { return val; }) - .Case([](evaluator::ReferenceValue *val) { - return cast(val->getStrippedValue()->get()); - })); + .Case([](evaluator::ListValue *val) { return val; })); }; bool hasUnknown = false; for (auto operand : op.getOperands()) { - auto result = evaluateValue(operand, actualParams, loc); + auto result = evaluateValue(operand, loc); if (failed(result)) return result; if (!result.value()->isFullyEvaluated()) @@ -1192,138 +783,12 @@ circt::om::Evaluator::evaluateListConcat(ListConcatOp op, return list; } -/// Evaluator dispatch function for String concatenation. -FailureOr -circt::om::Evaluator::evaluateStringConcat(StringConcatOp op, - ActualParameters actualParams, - Location loc) { - // Get the op's EvaluatorValue handle, in case it hasn't been evaluated yet. - auto handle = getOrCreateValue(op.getResult(), actualParams, loc); - if (failed(handle)) - return handle; - - // If it's fully evaluated, we can return it. - if (handle.value()->isFullyEvaluated()) - return handle; - - // Extract the string attributes, handling both AttributeValue and - // ReferenceValue cases. - auto extractAttr = [](evaluator::EvaluatorValue *value) -> StringAttr { - return llvm::TypeSwitch(value) - .Case([](evaluator::AttributeValue *val) { - return val->getAs(); - }) - .Case([](evaluator::ReferenceValue *val) { - return cast(val->getStrippedValue()->get()) - ->getAs(); - }); - }; - - // Evaluate all operands and concatenate them. - std::string result; - for (auto operand : op.getOperands()) { - auto operandResult = evaluateValue(operand, actualParams, loc); - if (failed(operandResult)) - return operandResult; - if (!operandResult.value()->isFullyEvaluated()) - return handle; - - StringAttr str = extractAttr(operandResult.value().get()); - assert(str && "expected StringAttr for StringConcatOp operand"); - result += str.getValue().str(); - } - - // Create the concatenated string attribute. - auto resultStr = StringAttr::get(result, op.getResult().getType()); - - // Finalize the op result value. - auto *handleValue = cast(handle.value().get()); - auto resultStatus = handleValue->setAttr(resultStr); - if (failed(resultStatus)) - return resultStatus; - - auto finalizeStatus = handleValue->finalize(); - if (failed(finalizeStatus)) - return finalizeStatus; - - return handle; -} - -// Evaluator dispatch function for binary property equality operations. -FailureOr -circt::om::Evaluator::evaluateBinaryEquality(BinaryEqualityOp op, - ActualParameters actualParams, - Location loc) { - // Get the op's EvaluatorValue handle, in case it hasn't been evaluated yet. - auto handle = getOrCreateValue(op.getResult(), actualParams, loc); - if (failed(handle)) - return handle; - - // If it's fully evaluated, we can return it. - if (handle.value()->isFullyEvaluated()) - return handle; - - // Evaluate both operands, returning the partially evaluated handle if either - // isn't ready yet. - auto lhsResult = evaluateValue(op.getLhs(), actualParams, loc); - if (failed(lhsResult)) - return lhsResult; - if (!lhsResult.value()->isFullyEvaluated()) - return handle; - - auto rhsResult = evaluateValue(op.getRhs(), actualParams, loc); - if (failed(rhsResult)) - return rhsResult; - if (!rhsResult.value()->isFullyEvaluated()) - return handle; - - // Check if any operand is unknown and propagate the unknown flag. - if (lhsResult.value()->isUnknown() || rhsResult.value()->isUnknown()) { - handle.value()->markUnknown(); - return handle; - } - - // Extract the underlying attribute, handling both AttributeValue and - // ReferenceValue cases. - auto extractAttr = [](evaluator::EvaluatorValue *value) -> mlir::Attribute { - return llvm::TypeSwitch(value) - .Case([](evaluator::AttributeValue *val) { return val->getAttr(); }) - .Case([](evaluator::ReferenceValue *val) -> mlir::Attribute { - return cast(val->getStrippedValue()->get()) - ->getAttr(); - }); - }; - - mlir::Attribute lhs = extractAttr(lhsResult.value().get()); - mlir::Attribute rhs = extractAttr(rhsResult.value().get()); - assert(lhs && rhs && "expected attribute for BinaryEqualityOp operands"); - - // Perform the binary equality operation. - FailureOr result = op.evaluateBinaryEquality(lhs, rhs); - if (failed(result)) - return op->emitError("failed to evaluate binary equality operation"); - - // Finalize the op result value. - auto *handleValue = cast(handle.value().get()); - auto resultStatus = handleValue->setAttr(*result); - if (failed(resultStatus)) - return resultStatus; - - auto finalizeStatus = handleValue->finalize(); - if (failed(finalizeStatus)) - return finalizeStatus; - - return handle; -} - FailureOr -circt::om::Evaluator::evaluateBasePathCreate(FrozenBasePathCreateOp op, - ActualParameters actualParams, - Location loc) { +circt::om::Evaluator::evaluateOp(FrozenBasePathCreateOp op, Location loc) { // Evaluate the Object itself, in case it hasn't been evaluated yet. - auto valueResult = getOrCreateValue(op, actualParams, loc).value(); + auto valueResult = getOrCreateValue(op, loc).value(); auto *path = llvm::cast(valueResult.get()); - auto result = evaluateValue(op.getBasePath(), actualParams, loc); + auto result = evaluateValue(op.getBasePath(), loc); if (failed(result)) return result; auto &value = result.value(); @@ -1341,13 +806,11 @@ circt::om::Evaluator::evaluateBasePathCreate(FrozenBasePathCreateOp op, } FailureOr -circt::om::Evaluator::evaluatePathCreate(FrozenPathCreateOp op, - ActualParameters actualParams, - Location loc) { +circt::om::Evaluator::evaluateOp(FrozenPathCreateOp op, Location loc) { // Evaluate the Object itself, in case it hasn't been evaluated yet. - auto valueResult = getOrCreateValue(op, actualParams, loc).value(); + auto valueResult = getOrCreateValue(op, loc).value(); auto *path = llvm::cast(valueResult.get()); - auto result = evaluateValue(op.getBasePath(), actualParams, loc); + auto result = evaluateValue(op.getBasePath(), loc); if (failed(result)) return result; auto &value = result.value(); @@ -1364,9 +827,9 @@ circt::om::Evaluator::evaluatePathCreate(FrozenPathCreateOp op, return valueResult; } -FailureOr circt::om::Evaluator::evaluateEmptyPath( - FrozenEmptyPathOp op, ActualParameters actualParams, Location loc) { - auto valueResult = getOrCreateValue(op, actualParams, loc).value(); +FailureOr +circt::om::Evaluator::evaluateOp(FrozenEmptyPathOp op, Location loc) { + auto valueResult = getOrCreateValue(op, loc).value(); return valueResult; } @@ -1417,7 +880,7 @@ circt::om::Evaluator::createUnknownValue(Type type, Location loc) { /// Evaluate an unknown value FailureOr -circt::om::Evaluator::evaluateUnknownValue(UnknownValueOp op, Location loc) { +circt::om::Evaluator::evaluateOp(UnknownValueOp op, Location loc) { return createUnknownValue(op.getType(), loc); } @@ -1448,42 +911,6 @@ ArrayAttr circt::om::Object::getFieldNames() { return ArrayAttr::get(cls.getContext(), fieldNames); } -LogicalResult circt::om::evaluator::ObjectValue::finalizeImpl() { - for (auto &&[e, value] : fields) - if (failed(finalizeEvaluatorValue(value))) - return failure(); - - return success(); -} - -//===----------------------------------------------------------------------===// -// ReferenceValue -//===----------------------------------------------------------------------===// - -LogicalResult circt::om::evaluator::ReferenceValue::finalizeImpl() { - auto result = getStrippedValue(); - if (failed(result)) - return result; - value = std::move(result.value()); - // the stripped value also needs to be finalized - if (failed(finalizeEvaluatorValue(value))) - return failure(); - - return success(); -} - -//===----------------------------------------------------------------------===// -// ListValue -//===----------------------------------------------------------------------===// - -LogicalResult circt::om::evaluator::ListValue::finalizeImpl() { - for (auto &value : elements) { - if (failed(finalizeEvaluatorValue(value))) - return failure(); - } - return success(); -} - //===----------------------------------------------------------------------===// // BasePathValue //===----------------------------------------------------------------------===// @@ -1598,13 +1025,6 @@ LogicalResult circt::om::evaluator::AttributeValue::setAttr(Attribute attr) { return success(); } -LogicalResult circt::om::evaluator::AttributeValue::finalizeImpl() { - if (!isFullyEvaluated()) - return mlir::emitError( - getLoc(), "cannot finalize AttributeValue that is not fully evaluated"); - return success(); -} - std::shared_ptr circt::om::evaluator::AttributeValue::get(Attribute attr, LocationAttr loc) { auto type = cast(attr).getType(); diff --git a/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp b/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp index bed727ce4d9e..a1f1de03d55d 100644 --- a/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp +++ b/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp @@ -38,13 +38,9 @@ struct EvaluatorTestContext { context.getOrLoadDialect(); } - OwningOpRef parseModule(StringRef moduleText, - bool skipElaborationTransform = false) { + OwningOpRef parseModule(StringRef moduleText) { OwningOpRef owning = parseSourceString(moduleText, ParserConfig(&context)); - if (owning && skipElaborationTransform) - owning.get()->setAttr("om.skip_elaboration_transform", - UnitAttr::get(&context)); return owning; } @@ -104,29 +100,19 @@ getListElements(const evaluator::EvaluatorValuePtr &value) { return llvm::cast(value.get())->getElements(); } -class EvaluatorTests : public ::testing::TestWithParam { +class EvaluatorTests : public ::testing::Test { protected: OwningOpRef parseModule(StringRef moduleText) { - return test.parseModule(moduleText, GetParam()); - } - - OwningOpRef parseModule(StringRef moduleText, - bool skipElaborationTransform) { - return test.parseModule(moduleText, skipElaborationTransform); + return test.parseModule(moduleText); } EvaluatorTestContext test; MLIRContext &context = test.context; }; -std::string getEvaluatorFlowName(const ::testing::TestParamInfo &info) { - return info.param ? "WithoutElaborationTransform" - : "WithElaborationTransform"; -} - /// Failure scenarios. -TEST_P(EvaluatorTests, InstantiateInvalidClassName) { +TEST_F(EvaluatorTests, InstantiateInvalidClassName) { StringRef mod = R"MLIR( module { } @@ -145,7 +131,7 @@ module { ASSERT_FALSE(succeeded(result)); } -TEST_P(EvaluatorTests, InstantiateInvalidParamSize) { +TEST_F(EvaluatorTests, InstantiateInvalidParamSize) { StringRef mod = R"MLIR( module { om.class @MyClass(%param: !om.integer) { @@ -170,7 +156,7 @@ module { ASSERT_FALSE(succeeded(result)); } -TEST_P(EvaluatorTests, InstantiateNullParam) { +TEST_F(EvaluatorTests, InstantiateNullParam) { StringRef mod = R"MLIR( module { om.class @MyClass(%param: !om.integer) { @@ -193,7 +179,7 @@ module { ASSERT_FALSE(succeeded(result)); } -TEST_P(EvaluatorTests, InstantiateInvalidParamType) { +TEST_F(EvaluatorTests, InstantiateInvalidParamType) { StringRef mod = R"MLIR( module { om.class @MyClass(%param: !om.integer) { @@ -218,7 +204,7 @@ module { ASSERT_FALSE(succeeded(result)); } -TEST_P(EvaluatorTests, GetFieldInvalidName) { +TEST_F(EvaluatorTests, GetFieldInvalidName) { StringRef mod = R"MLIR( module { om.class @MyClass() { @@ -247,7 +233,7 @@ module { /// Success scenarios. -TEST_P(EvaluatorTests, InstantiateObjectWithParamField) { +TEST_F(EvaluatorTests, InstantiateObjectWithParamField) { StringRef mod = R"MLIR( module { om.class @MyClass(%param: !om.integer) -> (field: !om.integer) { @@ -267,7 +253,7 @@ module { ASSERT_EQ(42, getInteger(getField(result.value(), "field"))); } -TEST_P(EvaluatorTests, InstantiateWithStructuredInputs) { +TEST_F(EvaluatorTests, InstantiateWithStructuredInputs) { StringRef mod = R"MLIR( module { om.class @Leaf(%value: !om.integer) -> (value: !om.integer) { @@ -328,10 +314,7 @@ module { ASSERT_EQ(anyFieldValue->getClassOp().getSymNameAttr().getValue(), "Leaf"); } -TEST_P(EvaluatorTests, InstantiateWithPartiallyEvaluatedInputs) { - if (GetParam()) - return; - +TEST_F(EvaluatorTests, InstantiateWithPartiallyEvaluatedInputs) { auto runFailure = [&](StringRef mod, StringRef className, evaluator::EvaluatorValuePtr value, StringRef expectedError) { @@ -378,7 +361,7 @@ module { "cannot import partially evaluated OM list value"); } -TEST_P(EvaluatorTests, InstantiateObjectWithConstantField) { +TEST_F(EvaluatorTests, InstantiateObjectWithConstantField) { StringRef mod = R"MLIR( module { om.class @MyClass() -> (field: !om.integer) { @@ -398,7 +381,7 @@ module { ASSERT_EQ(42, getInteger(getField(result.value(), "field"))); } -TEST_P(EvaluatorTests, InstantiateObjectWithChildObject) { +TEST_F(EvaluatorTests, InstantiateObjectWithChildObject) { StringRef mod = R"MLIR( module { om.class @MyInnerClass(%param: !om.integer) -> (field: !om.integer) { @@ -424,7 +407,7 @@ module { ASSERT_EQ(42, getInteger(getField(fieldValue, "field"))); } -TEST_P(EvaluatorTests, InstantiateObjectWithFieldAccess) { +TEST_F(EvaluatorTests, InstantiateObjectWithFieldAccess) { StringRef mod = R"MLIR( module { om.class @MyInnerClass(%param: !om.integer) -> (field: !om.integer) { @@ -450,7 +433,7 @@ module { ASSERT_EQ(42, getInteger(getField(result.value(), "field"))); } -TEST_P(EvaluatorTests, InstantiateObjectWithChildObjectMemoized) { +TEST_F(EvaluatorTests, InstantiateObjectWithChildObjectMemoized) { StringRef mod = R"MLIR( module { om.class @MyInnerClass() { @@ -489,7 +472,7 @@ module { ASSERT_EQ(field1Value, field2Value); } -TEST_P(EvaluatorTests, AnyCastObject) { +TEST_F(EvaluatorTests, AnyCastObject) { StringRef mod = R"MLIR( module { om.class @MyInnerClass() { @@ -515,7 +498,7 @@ module { ASSERT_EQ(fieldValue->getClassOp().getSymName(), "MyInnerClass"); } -TEST_P(EvaluatorTests, AnyCastParam) { +TEST_F(EvaluatorTests, AnyCastParam) { StringRef mod = R"MLIR( module { om.class @MyInnerClass(%param: !om.any) -> (field: !om.any) { @@ -545,7 +528,7 @@ module { ASSERT_EQ(42u, getBuiltinInteger(getField(fieldValue, "field"))); } -TEST_P(EvaluatorTests, InstantiateGraphRegion) { +TEST_F(EvaluatorTests, InstantiateGraphRegion) { StringRef mod = R"MLIR( !ty = !om.class.type<@LinkedList> om.class @LinkedList(%n: !ty, %val: !om.string) -> (n: !ty, val: @@ -586,24 +569,15 @@ om.class @UseNode(%node: !ty) -> (node: !ty) { auto node = getField(result.value(), "field1"); - if (!GetParam()) { - context.getDiagEngine().registerHandler([&](Diagnostic &diag) { - ASSERT_EQ(diag.str(), "cannot import mutually referential OM objects"); - }); - auto useNodeResult = - evaluator.instantiate(StringAttr::get(&context, "UseNode"), {node}); - ASSERT_TRUE(failed(useNodeResult)); - return; - } - + context.getDiagEngine().registerHandler([&](Diagnostic &diag) { + ASSERT_EQ(diag.str(), "cannot import mutually referential OM objects"); + }); auto useNodeResult = evaluator.instantiate(StringAttr::get(&context, "UseNode"), {node}); - ASSERT_TRUE(succeeded(useNodeResult)); - auto resultNode = getField(useNodeResult.value(), "node"); - ASSERT_EQ(resultNode.get(), node.get()); + ASSERT_TRUE(failed(useNodeResult)); } -TEST_P(EvaluatorTests, InstantiateCycle) { +TEST_F(EvaluatorTests, InstantiateCycle) { StringRef mod = R"MLIR( !ty = !om.class.type<@LinkedList> om.class @LinkedList(%n: !ty) -> (n: !ty){ @@ -617,11 +591,7 @@ om.class @ReferenceEachOther() -> (field: !ty){ )MLIR"; context.getDiagEngine().registerHandler([&](Diagnostic &diag) { - ASSERT_EQ(diag.str(), - GetParam() - ? "cycle detected: 1 values remain partially evaluated after " - "full pass with no progress (total fully evaluated: 1)" - : "failed to evaluate om.object.field"); + ASSERT_EQ(diag.str(), "failed to evaluate om.object.field"); }); OwningOpRef owning = parseModule(mod); @@ -637,7 +607,7 @@ om.class @ReferenceEachOther() -> (field: !ty){ // Test nested object field references. // https://github.com/llvm/circt/issues/10264 -TEST_P(EvaluatorTests, Issue10264NestedFieldReferences) { +TEST_F(EvaluatorTests, Issue10264NestedFieldReferences) { StringRef mod = R"MLIR( om.class @Domain(%in: !om.string) -> (out: !om.string) { om.class.fields %in : !om.string @@ -666,7 +636,7 @@ om.class @Top() -> (test: i1) { ASSERT_FALSE(getBool(getField(result.value(), "test"))); } -TEST_P(EvaluatorTests, IntegerBinaryArithmeticAdd) { +TEST_F(EvaluatorTests, IntegerBinaryArithmeticAdd) { StringRef mod = R"MLIR( om.class @IntegerBinaryArithmeticAdd() -> (result: !om.integer) { %0 = om.constant #om.integer<1 : si3> : !om.integer @@ -688,7 +658,7 @@ om.class @IntegerBinaryArithmeticAdd() -> (result: !om.integer) { ASSERT_EQ(getInteger(getField(result.value(), "result")), 3); } -TEST_P(EvaluatorTests, IntegerBinaryArithmeticMul) { +TEST_F(EvaluatorTests, IntegerBinaryArithmeticMul) { StringRef mod = R"MLIR( om.class @IntegerBinaryArithmeticMul() -> (result: !om.integer) { %0 = om.constant #om.integer<2 : si3> : !om.integer @@ -710,7 +680,7 @@ om.class @IntegerBinaryArithmeticMul() -> (result: !om.integer) { ASSERT_EQ(6, getInteger(getField(result.value(), "result"))); } -TEST_P(EvaluatorTests, IntegerBinaryArithmeticShr) { +TEST_F(EvaluatorTests, IntegerBinaryArithmeticShr) { StringRef mod = R"MLIR( om.class @IntegerBinaryArithmeticShr() -> (result: !om.integer){ %0 = om.constant #om.integer<8 : si5> : !om.integer @@ -732,7 +702,7 @@ om.class @IntegerBinaryArithmeticShr() -> (result: !om.integer){ ASSERT_EQ(2, getInteger(getField(result.value(), "result"))); } -TEST_P(EvaluatorTests, IntegerBinaryArithmeticShrNegative) { +TEST_F(EvaluatorTests, IntegerBinaryArithmeticShrNegative) { StringRef mod = R"MLIR( om.class @IntegerBinaryArithmeticShrNegative() -> (result: !om.integer){ %0 = om.constant #om.integer<8 : si5> : !om.integer @@ -747,8 +717,7 @@ om.class @IntegerBinaryArithmeticShrNegative() -> (result: !om.integer){ ASSERT_EQ(diag.str(), "'om.integer.shr' op shift amount must be non-negative"); if (StringRef(diag.str()).starts_with("failed")) - ASSERT_EQ(diag.str(), GetParam() ? "failed to evaluate integer operation" - : "failed to evaluate om.integer.shr"); + ASSERT_EQ(diag.str(), "failed to evaluate om.integer.shr"); }); OwningOpRef owning = parseModule(mod); @@ -762,7 +731,7 @@ om.class @IntegerBinaryArithmeticShrNegative() -> (result: !om.integer){ ASSERT_TRUE(failed(result)); } -TEST_P(EvaluatorTests, IntegerBinaryArithmeticShrTooLarge) { +TEST_F(EvaluatorTests, IntegerBinaryArithmeticShrTooLarge) { StringRef mod = R"MLIR( om.class @IntegerBinaryArithmeticShrTooLarge() -> (result: !om.integer){ %0 = om.constant #om.integer<8 : si5> : !om.integer @@ -778,8 +747,7 @@ om.class @IntegerBinaryArithmeticShrTooLarge() -> (result: !om.integer){ diag.str(), "'om.integer.shr' op shift amount must be representable in 64 bits"); if (StringRef(diag.str()).starts_with("failed")) - ASSERT_EQ(diag.str(), GetParam() ? "failed to evaluate integer operation" - : "failed to evaluate om.integer.shr"); + ASSERT_EQ(diag.str(), "failed to evaluate om.integer.shr"); }); OwningOpRef owning = parseModule(mod); @@ -793,7 +761,7 @@ om.class @IntegerBinaryArithmeticShrTooLarge() -> (result: !om.integer){ ASSERT_TRUE(failed(result)); } -TEST_P(EvaluatorTests, IntegerBinaryArithmeticShl) { +TEST_F(EvaluatorTests, IntegerBinaryArithmeticShl) { StringRef mod = R"MLIR( om.class @IntegerBinaryArithmeticShl() -> (result: !om.integer){ %0 = om.constant #om.integer<8 : si7> : !om.integer @@ -815,7 +783,7 @@ om.class @IntegerBinaryArithmeticShl() -> (result: !om.integer){ ASSERT_EQ(32, getInteger(getField(result.value(), "result"))); } -TEST_P(EvaluatorTests, IntegerBinaryArithmeticShlNegative) { +TEST_F(EvaluatorTests, IntegerBinaryArithmeticShlNegative) { StringRef mod = R"MLIR( om.class @IntegerBinaryArithmeticShlNegative() -> (result: !om.integer) { %0 = om.constant #om.integer<8 : si5> : !om.integer @@ -830,8 +798,7 @@ om.class @IntegerBinaryArithmeticShlNegative() -> (result: !om.integer) { ASSERT_EQ(diag.str(), "'om.integer.shl' op shift amount must be non-negative"); if (StringRef(diag.str()).starts_with("failed")) - ASSERT_EQ(diag.str(), GetParam() ? "failed to evaluate integer operation" - : "failed to evaluate om.integer.shl"); + ASSERT_EQ(diag.str(), "failed to evaluate om.integer.shl"); }); OwningOpRef owning = parseModule(mod); @@ -845,7 +812,7 @@ om.class @IntegerBinaryArithmeticShlNegative() -> (result: !om.integer) { ASSERT_TRUE(failed(result)); } -TEST_P(EvaluatorTests, IntegerBinaryArithmeticShlTooLarge) { +TEST_F(EvaluatorTests, IntegerBinaryArithmeticShlTooLarge) { StringRef mod = R"MLIR( om.class @IntegerBinaryArithmeticShlTooLarge() -> (result: !om.integer) { %0 = om.constant #om.integer<8 : si5> : !om.integer @@ -861,8 +828,7 @@ om.class @IntegerBinaryArithmeticShlTooLarge() -> (result: !om.integer) { diag.str(), "'om.integer.shl' op shift amount must be representable in 64 bits"); if (StringRef(diag.str()).starts_with("failed")) - ASSERT_EQ(diag.str(), GetParam() ? "failed to evaluate integer operation" - : "failed to evaluate om.integer.shl"); + ASSERT_EQ(diag.str(), "failed to evaluate om.integer.shl"); }); OwningOpRef owning = parseModule(mod); @@ -876,7 +842,7 @@ om.class @IntegerBinaryArithmeticShlTooLarge() -> (result: !om.integer) { ASSERT_TRUE(failed(result)); } -TEST_P(EvaluatorTests, IntegerBinaryArithmeticObjects) { +TEST_F(EvaluatorTests, IntegerBinaryArithmeticObjects) { StringRef mod = R"MLIR( om.class @Class1() -> (value: !om.integer){ %0 = om.constant #om.integer<1 : si3> : !om.integer @@ -912,7 +878,7 @@ om.class @IntegerBinaryArithmeticObjects() -> (result: !om.integer) { ASSERT_EQ(3, getInteger(getField(result.value(), "result"))); } -TEST_P(EvaluatorTests, IntegerBinaryArithmeticObjectsDelayed) { +TEST_F(EvaluatorTests, IntegerBinaryArithmeticObjectsDelayed) { StringRef mod = R"MLIR( om.class @Class1(%input: !om.integer) -> (value: !om.integer, input: !om.integer) { %0 = om.constant #om.integer<1 : si3> : !om.integer @@ -948,7 +914,7 @@ om.class @IntegerBinaryArithmeticObjectsDelayed() -> (result: !om.integer){ ASSERT_EQ(3, getInteger(getField(result.value(), "result"))); } -TEST_P(EvaluatorTests, IntegerBinaryArithmeticWidthMismatch) { +TEST_F(EvaluatorTests, IntegerBinaryArithmeticWidthMismatch) { StringRef mod = R"MLIR( om.class @IntegerBinaryArithmeticWidthMismatch() -> (result: !om.integer) { %0 = om.constant #om.integer<1 : si3> : !om.integer @@ -970,7 +936,7 @@ om.class @IntegerBinaryArithmeticWidthMismatch() -> (result: !om.integer) { ASSERT_EQ(3, getInteger(getField(result.value(), "result"))); } -TEST_P(EvaluatorTests, ListConcat) { +TEST_F(EvaluatorTests, ListConcat) { StringRef mod = R"MLIR( om.class @ListConcat() -> (result: !om.list) { %0 = om.constant #om.integer<0 : i8> : !om.integer @@ -1000,7 +966,7 @@ om.class @ListConcat() -> (result: !om.list) { ASSERT_EQ(2, getInteger(finalList[2])); } -TEST_P(EvaluatorTests, ListConcatField) { +TEST_F(EvaluatorTests, ListConcatField) { StringRef mod = R"MLIR( om.class @ListField() -> (value: !om.list) { %0 = om.constant #om.integer<2 : i8> : !om.integer @@ -1035,7 +1001,7 @@ om.class @ListConcatField() -> (result: !om.list){ ASSERT_EQ(2, getInteger(finalList[2])); } -TEST_P(EvaluatorTests, ListOfListConcat) { +TEST_F(EvaluatorTests, ListOfListConcat) { StringRef mod = R"MLIR( om.class @ListOfListConcat() -> (result: !om.list>) { %0 = om.constant "foo" : !om.string @@ -1073,7 +1039,7 @@ om.class @ListOfListConcat() -> (result: !om.list>) { ASSERT_EQ("qux", getString(sublist1[1])); } -TEST_P(EvaluatorTests, ListConcatPartialCycle) { +TEST_F(EvaluatorTests, ListConcatPartialCycle) { StringRef mod = R"MLIR( om.class @Child(%field_in: !om.any) -> (field: !om.list) { %1 = om.list_create %field_in : !om.any @@ -1115,7 +1081,7 @@ om.class @ListConcatPartialCycle() -> (result: !om.list){ ASSERT_EQ(2, getInteger(getField(finalList[1], "id"))); } -TEST_P(EvaluatorTests, NestedReferenceValue) { +TEST_F(EvaluatorTests, NestedFieldValues) { StringRef mod = R"MLIR( om.class @Empty() { om.class.fields @@ -1172,7 +1138,7 @@ om.class @OuterClass1() -> (om: !om.any) { ASSERT_TRUE(isa(anyList2[0].get())); } -TEST_P(EvaluatorTests, ListAttrConcat) { +TEST_F(EvaluatorTests, ListAttrConcat) { StringRef mod = R"MLIR( om.class @ConcatListAttribute() -> (result: !om.list) { %0 = om.constant #om.list : !om.list @@ -1203,7 +1169,7 @@ om.class @ConcatListAttribute() -> (result: !om.list) { checkEq(listVal[3], "Y"); } -TEST_P(EvaluatorTests, UnknownValuesBasic) { +TEST_F(EvaluatorTests, UnknownValuesBasic) { StringRef mod = R"MLIR( om.class.extern @Baz() -> (a: !om.integer) {} @@ -1338,7 +1304,7 @@ om.class @Foo( checkFieldValueType("q", Kind::Object); // external class -> ObjectValue } -TEST_P(EvaluatorTests, UnknownValuesNested) { +TEST_F(EvaluatorTests, UnknownValuesNested) { StringRef mod = R"MLIR( om.class @Bar( %known_in: !om.integer, @@ -1386,7 +1352,7 @@ om.class @Foo( ASSERT_TRUE(getField(result.value(), "b")->isUnknown()); } -TEST_P(EvaluatorTests, StringConcat) { +TEST_F(EvaluatorTests, StringConcat) { const char *mod = R"MLIR( module { om.class @Test() -> (result: !om.string) { @@ -1413,7 +1379,7 @@ module { ASSERT_EQ("Hello, World!", getString(getField(result.value(), "result"))); } -TEST_P(EvaluatorTests, UnknownObjectFieldTest) { +TEST_F(EvaluatorTests, UnknownObjectFieldTest) { StringRef mod = R"MLIR( om.class.extern @Dut_Class(%basepath: !om.frozenbasepath) -> (omirOut: !om.list) { } @@ -1442,7 +1408,7 @@ om.class @TestHarness_Class(%basepath: !om.frozenbasepath) -> (result: !om.list< EXPECT_TRUE(getField(result.value(), "result")->isUnknown()); } -TEST_P(EvaluatorTests, PropertyAssertTests) { +TEST_F(EvaluatorTests, PropertyAssertTests) { StringRef mod = R"MLIR( // Test 1: A true assert passes. om.class @True() -> () { @@ -1598,8 +1564,7 @@ om.class @ChainedDomainAssert(%basepath: !om.frozenbasepath) -> () { evaluator.instantiate(StringAttr::get(&context, "SubfieldFalse"), {}))); // Test 11: Two asserts on a value flowing through chained object instances. - // Both assertions fail; the evaluator must drain the worklist before - // evaluating either assert (i.e. the fix for the null-ReferenceValue crash). + // Both assertions fail. auto basepath = std::make_shared(&context); ASSERT_TRUE(failed(evaluator.instantiate( StringAttr::get(&context, "ChainedDomainAssert"), {basepath}))); @@ -1616,7 +1581,7 @@ om.class @ChainedDomainAssert(%basepath: !om.frozenbasepath) -> () { EXPECT_EQ(actual, expected); } -TEST_P(EvaluatorTests, PropEqTests) { +TEST_F(EvaluatorTests, PropEqTests) { StringRef mod = R"MLIR( om.class @PropEqString(%s: !om.string) -> (equal: i1, not_equal: i1, unknown: i1) { %a = om.constant "hello" : !om.string @@ -1676,7 +1641,7 @@ om.class @PropEqInteger(%n: !om.integer) -> (equal: i1, not_equal: i1, unknown: } } -TEST_P(EvaluatorTests, IntegerBitwiseTests) { +TEST_F(EvaluatorTests, IntegerBitwiseTests) { StringRef mod = R"MLIR( om.class @IntegerBitwiseAnd(%a: i8, %b: i8) -> (result: i8) { %and = om.integer.and %a, %b : i8 @@ -1713,7 +1678,6 @@ om.class @IntegerBitwiseUnknown(%b: i8) -> (unknown: i8) { auto attr = mlir::IntegerAttr::get(i8Type, val); auto v = evaluator::AttributeValue::get(i8Type, unknownLoc); (void)cast(v.get())->setAttr(attr); - (void)cast(v.get())->finalize(); return v; }; @@ -1762,11 +1726,8 @@ om.class @IntegerBitwiseUnknown(%b: i8) -> (unknown: i8) { auto r = evaluator.instantiate( StringAttr::get(&context, "IntegerBitwiseUnknown"), {unknown}); ASSERT_TRUE(succeeded(r)); - ASSERT_EQ(getField(r.value(), "unknown")->isUnknown(), GetParam()); + ASSERT_FALSE(getField(r.value(), "unknown")->isUnknown()); } } -INSTANTIATE_TEST_SUITE_P(EvaluatorFlows, EvaluatorTests, - ::testing::Values(false, true), getEvaluatorFlowName); - } // namespace From 7fbd496f8567b526a2eec30c1fee95c3398d812c Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Thu, 28 May 2026 00:56:12 -0700 Subject: [PATCH 2/5] Match fused --- integration_test/Bindings/Python/dialects/om.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_test/Bindings/Python/dialects/om.py b/integration_test/Bindings/Python/dialects/om.py index ce7fa5fa6adf..55f54781b241 100644 --- a/integration_test/Bindings/Python/dialects/om.py +++ b/integration_test/Bindings/Python/dialects/om.py @@ -146,7 +146,7 @@ # CHECK: child.foo: 14 print("child.foo: ", obj.child.foo) -# CHECK: child.foo.loc loc("-":{{.*}}:{{.*}}) +# CHECK: child.foo.loc loc(fused["-":{{.*}}:{{.*}} print("child.foo.loc", obj.child.get_field_loc("foo")) # CHECK: ('Root', 'x') print(obj.reference) From b5cb6f6ff4692f82bcba00c3a910e8d19adce98d Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Thu, 28 May 2026 01:27:12 -0700 Subject: [PATCH 3/5] Modify CHECK-SAME loc entries in om.py Updated CHECK-SAME loc entries in om.py to use 'fused' instead of '-' --- integration_test/Bindings/Python/dialects/om.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration_test/Bindings/Python/dialects/om.py b/integration_test/Bindings/Python/dialects/om.py index 55f54781b241..23e88e91d806 100644 --- a/integration_test/Bindings/Python/dialects/om.py +++ b/integration_test/Bindings/Python/dialects/om.py @@ -157,10 +157,10 @@ # CHECK-SAME: loc: loc(fused # location from om.class.field "field" # CHECK: name: field, field: 42 - # CHECK-SAME: loc: loc("-":{{.*}}:{{.*}}) + # CHECK-SAME: loc: loc(fused # location from om.class.field "reference" # CHECK: name: reference, field: ('Root', 'x') - # CHECK-SAME: loc: loc("-":{{.*}}:{{.*}}) + # CHECK-SAME: loc: loc(fused loc = obj.get_field_loc(name) print(f"name: {name}, field: {field}, loc: {loc}") From f2fefb3545de1b3b2c2e0356dab31b606b764c30 Mon Sep 17 00:00:00 2001 From: Hideto Ueno Date: Thu, 28 May 2026 02:05:38 -0700 Subject: [PATCH 4/5] Update CHECK comments for field locations in om.py --- integration_test/Bindings/Python/dialects/om.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integration_test/Bindings/Python/dialects/om.py b/integration_test/Bindings/Python/dialects/om.py index 23e88e91d806..d865ba325d42 100644 --- a/integration_test/Bindings/Python/dialects/om.py +++ b/integration_test/Bindings/Python/dialects/om.py @@ -141,12 +141,12 @@ print(obj.field) # location of the om.class.field @field -# CHECK: field: loc("-":{{.*}}:{{.*}}) +# CHECK: field: loc print("field:", obj.get_field_loc("field")) # CHECK: child.foo: 14 print("child.foo: ", obj.child.foo) -# CHECK: child.foo.loc loc(fused["-":{{.*}}:{{.*}} +# CHECK: child.foo.loc loc print("child.foo.loc", obj.child.get_field_loc("foo")) # CHECK: ('Root', 'x') print(obj.reference) @@ -154,13 +154,13 @@ for (name, field) in obj: # location from om.class.field "child" # CHECK: name: child, field: Date: Thu, 28 May 2026 11:09:41 -0700 Subject: [PATCH 5/5] Change error handling to return failure instead of error --- lib/Dialect/OM/Evaluator/Evaluator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Dialect/OM/Evaluator/Evaluator.cpp b/lib/Dialect/OM/Evaluator/Evaluator.cpp index d838a5e9ad50..ad11d85ebdd0 100644 --- a/lib/Dialect/OM/Evaluator/Evaluator.cpp +++ b/lib/Dialect/OM/Evaluator/Evaluator.cpp @@ -399,7 +399,7 @@ circt::om::Evaluator::getOrCreateValue(Value value, Location loc) { auto error = arg.getOwner()->getParentOp()->emitError( "unable to evaluate unbound parameter"); error.attachNote() << "value: " << value; - return error; + return failure(); }) .Case([&](OpResult result) { return TypeSwitch