-
Notifications
You must be signed in to change notification settings - Fork 467
[OM] Fix object field value is considered fully evaluated even it's not #10293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,32 +70,38 @@ FailureOr<evaluator::EvaluatorValuePtr> | |
| circt::om::Evaluator::getPartiallyEvaluatedValue(Type type, Location loc) { | ||
| using namespace circt::om::evaluator; | ||
|
|
||
| return TypeSwitch<mlir::Type, FailureOr<evaluator::EvaluatorValuePtr>>(type) | ||
| .Case([&](circt::om::ListType type) { | ||
| evaluator::EvaluatorValuePtr result = | ||
| std::make_shared<evaluator::ListValue>(type, loc); | ||
| return success(result); | ||
| }) | ||
| .Case([&](circt::om::ClassType type) | ||
| -> FailureOr<evaluator::EvaluatorValuePtr> { | ||
| auto classDef = | ||
| symbolTable.lookup<ClassLike>(type.getClassName().getValue()); | ||
| if (!classDef) | ||
| return symbolTable.getOp()->emitError("unknown class name ") | ||
| << type.getClassName(); | ||
|
|
||
| // Create an ObjectValue for both ClassOp and ClassExternOp | ||
| evaluator::EvaluatorValuePtr result = | ||
| std::make_shared<evaluator::ObjectValue>(classDef, loc); | ||
|
|
||
| return success(result); | ||
| }) | ||
| .Case([&](circt::om::StringType type) { | ||
| evaluator::EvaluatorValuePtr result = | ||
| evaluator::AttributeValue::get(type, loc); | ||
| return success(result); | ||
| }) | ||
| .Default([&](auto type) { return failure(); }); | ||
| auto result = | ||
| TypeSwitch<mlir::Type, FailureOr<evaluator::EvaluatorValuePtr>>(type) | ||
| .Case([&](circt::om::ListType type) { | ||
| evaluator::EvaluatorValuePtr result = | ||
| std::make_shared<evaluator::ListValue>(type, loc); | ||
| return success(result); | ||
| }) | ||
| .Case([&](circt::om::ClassType type) | ||
| -> FailureOr<evaluator::EvaluatorValuePtr> { | ||
| auto classDef = | ||
| symbolTable.lookup<ClassLike>(type.getClassName().getValue()); | ||
| if (!classDef) | ||
| return symbolTable.getOp()->emitError("unknown class name ") | ||
| << type.getClassName(); | ||
|
|
||
| // Create an ObjectValue for both ClassOp and ClassExternOp | ||
| evaluator::EvaluatorValuePtr result = | ||
| std::make_shared<evaluator::ObjectValue>(classDef, loc); | ||
|
|
||
| return success(result); | ||
| }) | ||
| .Case([&](circt::om::StringType type) { | ||
| evaluator::EvaluatorValuePtr result = | ||
| evaluator::AttributeValue::get(type, loc); | ||
| return success(result); | ||
| }) | ||
| .Default([&](auto type) { return failure(); }); | ||
|
|
||
| if (succeeded(result)) | ||
| attachCounter(result.value()); | ||
|
Comment on lines
+101
to
+102
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given the checking in
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think result.value() causes assertion failure for FailureOr::failuare(= std::nullopt), so we need to add a guard for this. |
||
|
|
||
| return result; | ||
| } | ||
|
|
||
| FailureOr<evaluator::EvaluatorValuePtr> circt::om::Evaluator::getOrCreateValue( | ||
|
|
@@ -186,6 +192,8 @@ FailureOr<evaluator::EvaluatorValuePtr> circt::om::Evaluator::getOrCreateValue( | |
| if (failed(result)) | ||
| return result; | ||
|
|
||
| // Attach listener to newly created values | ||
| attachCounter(result.value()); | ||
| objects[{value, actualParams}] = result.value(); | ||
| return result; | ||
| } | ||
|
|
@@ -212,6 +220,7 @@ circt::om::Evaluator::evaluateObjectInstance(StringAttr className, | |
| if (isa<ClassExternOp>(classDef)) { | ||
| evaluator::EvaluatorValuePtr result = | ||
| std::make_shared<evaluator::ObjectValue>(classDef, loc); | ||
| attachCounter(result); | ||
| result->markUnknown(); | ||
| LLVM_DEBUG(dbgs(1) << "extern: <unknown-value>\n"); | ||
| return result; | ||
|
|
@@ -283,7 +292,7 @@ circt::om::Evaluator::evaluateObjectInstance(StringAttr className, | |
| UnknownLoc::get(context)))) | ||
| return failure(); | ||
| // Add to the worklist. | ||
| worklist.push({result, actualParams}); | ||
| worklist.push_back({result, actualParams}); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -329,6 +338,7 @@ circt::om::Evaluator::evaluateObjectInstance(StringAttr className, | |
| // If it's external call, just allocate new ObjectValue. | ||
| evaluator::EvaluatorValuePtr result = | ||
| std::make_shared<evaluator::ObjectValue>(cls, fields, loc); | ||
| // Note: Object is already fully evaluated when created with fields | ||
|
uenoku marked this conversation as resolved.
Outdated
|
||
| return result; | ||
| } | ||
|
|
||
|
|
@@ -355,6 +365,7 @@ circt::om::Evaluator::instantiate( | |
| evaluator::EvaluatorValuePtr result = | ||
| std::make_shared<evaluator::ObjectValue>( | ||
| classDef, UnknownLoc::get(classDef.getContext())); | ||
| attachCounter(result); | ||
| result->markUnknown(); | ||
| LLVM_DEBUG(dbgs(1) << "result: <unknown extern>\n"); | ||
| return result; | ||
|
|
@@ -380,18 +391,48 @@ circt::om::Evaluator::instantiate( | |
| // `evaluateObjectInstance` has populated the worklist. Continue evaluations | ||
| // unless there is a partially evaluated value. | ||
| LLVM_DEBUG(dbgs() << "worklist:\n"); | ||
| while (!worklist.empty()) { | ||
| auto [value, args] = worklist.front(); | ||
| worklist.pop(); | ||
|
|
||
| auto result = evaluateValue(value, args, loc); | ||
| // 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. | ||
|
uenoku marked this conversation as resolved.
Outdated
|
||
| 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 | ||
|
uenoku marked this conversation as resolved.
Outdated
|
||
| 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}); | ||
| } | ||
|
uenoku marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| if (failed(result)) | ||
| return failure(); | ||
| // 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 << ")"; | ||
| } | ||
|
uenoku marked this conversation as resolved.
Outdated
|
||
|
|
||
| // It's possible that the value is not fully evaluated. | ||
| if (!result.value()->isFullyEvaluated()) | ||
| worklist.push({value, args}); | ||
| // Swap worklists for next iteration | ||
| worklist = std::move(nextWorklist); | ||
| nextWorklist.clear(); | ||
| } | ||
|
|
||
| // Now that all values are fully resolved, evaluate the deferred property | ||
|
|
@@ -732,6 +773,9 @@ circt::om::Evaluator::evaluateObjectField(ObjectFieldOp op, | |
| currentObject = nextObject; | ||
| } | ||
|
|
||
| if (!finalField->isFullyEvaluated()) | ||
| return objectFieldValue; | ||
|
|
||
| // Update the reference. | ||
| llvm::cast<evaluator::ReferenceValue>(objectFieldValue.get()) | ||
| ->setValue(finalField); | ||
|
|
@@ -1045,8 +1089,10 @@ circt::om::Evaluator::createUnknownValue(Type type, Location loc) { | |
| }); | ||
|
|
||
| // Mark the result as unknown if successful | ||
| if (succeeded(result)) | ||
| if (succeeded(result)) { | ||
| attachCounter(result.value()); | ||
| result->get()->markUnknown(); | ||
| } | ||
|
uenoku marked this conversation as resolved.
Outdated
|
||
|
|
||
| return result; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -654,8 +654,9 @@ om.class @ReferenceEachOther() -> (field: !ty){ | |||||
| context.getOrLoadDialect<OMDialect>(); | ||||||
|
|
||||||
| context.getDiagEngine().registerHandler([&](Diagnostic &diag) { | ||||||
| ASSERT_EQ(diag.str(), "failed to finalize evaluation. Probably the class " | ||||||
| "contains a dataflow cycle"); | ||||||
| ASSERT_EQ(diag.str(), | ||||||
| "cycle detected: 1 values remain partially evaluated after full " | ||||||
| "pass with no progress (total fully evaluated: 1)"); | ||||||
| }); | ||||||
|
|
||||||
| OwningOpRef<ModuleOp> owning = | ||||||
|
|
@@ -669,6 +670,51 @@ om.class @ReferenceEachOther() -> (field: !ty){ | |||||
| ASSERT_TRUE(failed(result)); | ||||||
| } | ||||||
|
|
||||||
| // Test for issue #10264 - nested object field references that previously | ||||||
| // caused an assertion failure. This test verifies that the evaluator can | ||||||
| // properly handle nested field accesses without hitting null pointers. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Please include a direct link tot he GitHub issue. Also, please rewrite this LLM-generated comment.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a policy for preferring a direct link over issue number? I don't have a strong opinion but certainly I've used both #<issue_number> and URL without much consideration. Certainly LLVM also doesn't have a policy for that, can you add to CIRCT coding guideine? circt/test/Dialect/FIRRTL/legacy-wiring.mlir Lines 212 to 213 in babde3d
https://github.com/llvm/llvm-project/blob/5892e34a96131821256f89c0555ba68c23c91dbe/mlir/test/Analysis/test-match-reduction.mlir#L118
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. I can do that. There's a significant trend towards full links, but both are used: |
||||||
| TEST(EvaluatorTests, Issue10264NestedFieldReferences) { | ||||||
| StringRef mod = R"MLIR( | ||||||
| om.class @Domain(%in: !om.string) -> (out: !om.string) { | ||||||
| om.class.fields %in : !om.string | ||||||
| } | ||||||
|
|
||||||
| om.class @Top() -> (test: i1) { | ||||||
| %0 = om.constant "A" : !om.string | ||||||
| %1 = om.object @Domain(%0) : (!om.string) -> !om.class.type<@Domain> | ||||||
| %2 = om.object.field %1, [@out] : (!om.class.type<@Domain>) -> !om.string | ||||||
| %3 = om.object @Domain(%2) : (!om.string) -> !om.class.type<@Domain> | ||||||
| %4 = om.object.field %3, [@out] : (!om.class.type<@Domain>) -> !om.string | ||||||
| %5 = om.constant "B" : !om.string | ||||||
| %6 = om.prop.eq %4, %5 : !om.string | ||||||
| om.class.fields %6 : i1 | ||||||
| } | ||||||
| )MLIR"; | ||||||
|
|
||||||
| DialectRegistry registry; | ||||||
| registry.insert<OMDialect>(); | ||||||
|
|
||||||
| MLIRContext context(registry); | ||||||
| context.getOrLoadDialect<OMDialect>(); | ||||||
|
|
||||||
| OwningOpRef<ModuleOp> owning = | ||||||
| parseSourceString<ModuleOp>(mod, ParserConfig(&context)); | ||||||
|
|
||||||
| Evaluator evaluator(owning.release()); | ||||||
|
|
||||||
| auto result = evaluator.instantiate(StringAttr::get(&context, "Top"), {}); | ||||||
|
|
||||||
| ASSERT_TRUE(succeeded(result)); | ||||||
|
|
||||||
| // Verify the result is correct (false since "A" != "B") | ||||||
| auto fieldValue = llvm::cast<evaluator::ObjectValue>(result.value().get()) | ||||||
| ->getField("test") | ||||||
| .value(); | ||||||
| auto boolValue = llvm::cast<evaluator::AttributeValue>(fieldValue.get()) | ||||||
| ->getAs<BoolAttr>(); | ||||||
| ASSERT_FALSE(boolValue.getValue()); | ||||||
| } | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very happy to see this working. Nice! |
||||||
|
|
||||||
| TEST(EvaluatorTests, IntegerBinaryArithmeticAdd) { | ||||||
| StringRef mod = R"MLIR( | ||||||
| om.class @IntegerBinaryArithmeticAdd() -> (result: !om.integer) { | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.