-
Notifications
You must be signed in to change notification settings - Fork 466
[OM] Introduce explicit evaluator state and op patterns #10265
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
Draft
uenoku
wants to merge
3
commits into
llvm:main
Choose a base branch
from
uenoku:dev/hidetou/evaluator-framework
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,60 @@ class EvaluatorValue; | |
| /// primitive Attribute. Further refinement is expected. | ||
| using EvaluatorValuePtr = std::shared_ptr<EvaluatorValue>; | ||
|
|
||
| /// The evaluator tracks two different things: | ||
| /// | ||
| /// 1. Local state of one value object: `isSettled()` | ||
| /// | ||
| /// false -> this value object may still change | ||
| /// true -> this value object has finished its own local work | ||
| /// | ||
| /// 2. State of using a value handle: `ResolutionState` | ||
| /// | ||
| /// Pending -> the handle still cannot be used | ||
| /// Ready -> the handle can be used now | ||
| /// Failure -> evaluation hit a hard error | ||
| /// | ||
| /// These are not the same thing. A reference may itself be settled, but using | ||
| /// the handle may still be pending: | ||
| /// | ||
| /// ReferenceValue (settled = true) | ||
| /// | | ||
| /// v | ||
| /// pending target | ||
| /// | ||
| /// So `isSettled()` is about one value object, while `ResolvedValue` is about | ||
| /// whether the whole handle is usable. | ||
| /// | ||
| /// The evaluation state of a value handle. | ||
| enum class ResolutionState { | ||
| /// The handle can be used now. For references, this means the whole | ||
| /// reference chain leads to a settled value. | ||
| Ready, | ||
| /// Evaluation is not done yet. The handle itself may still be partial, or a | ||
| /// reference in the chain may still be missing. | ||
| Pending, | ||
| /// Evaluation hit a hard error, such as a reference cycle. | ||
| Failure | ||
| }; | ||
|
|
||
| struct ResolvedValue { | ||
| /// `state` says whether `value` can be used. `value` keeps the original | ||
| /// handle so callers can keep passing it around even when it is still pending | ||
| /// or has already failed. | ||
| ResolutionState state; | ||
| EvaluatorValuePtr value; | ||
|
Comment on lines
+76
to
+81
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. This is looking a lot like I realize that |
||
|
|
||
| static ResolvedValue ready(EvaluatorValuePtr value) { | ||
| return {ResolutionState::Ready, std::move(value)}; | ||
| } | ||
| static ResolvedValue pending(EvaluatorValuePtr value = nullptr) { | ||
| return {ResolutionState::Pending, std::move(value)}; | ||
| } | ||
| static ResolvedValue failure(EvaluatorValuePtr value = nullptr) { | ||
| return {ResolutionState::Failure, std::move(value)}; | ||
| } | ||
| }; | ||
|
|
||
| /// The fields of a composite Object, currently represented as a map. Further | ||
| /// refinement is expected. | ||
| using ObjectFields = SmallDenseMap<StringAttr, EvaluatorValuePtr>; | ||
|
|
@@ -54,12 +108,14 @@ class EvaluatorValue : public std::enable_shared_from_this<EvaluatorValue> { | |
| Kind getKind() const { return kind; } | ||
| MLIRContext *getContext() const { return ctx; } | ||
|
|
||
| // Return true the value is fully evaluated. | ||
| // Unknown values are considered fully evaluated. | ||
| bool isFullyEvaluated() const { return fullyEvaluated; } | ||
| void markFullyEvaluated() { | ||
| assert(!fullyEvaluated && "should not mark twice"); | ||
| fullyEvaluated = true; | ||
| // Return true if this value object has finished its own local work. | ||
| // This is not the same as semantic Ready/Pending state: for example, a | ||
| // ReferenceValue can be settled but still point to a pending value. | ||
| // Unknown values are considered settled. | ||
| bool isSettled() const { return settled; } | ||
| void markSettled() { | ||
| assert(!settled && "should not mark twice"); | ||
| settled = true; | ||
| } | ||
|
|
||
| /// Return true if the value is unknown (has unknown in its fan-in). | ||
|
|
@@ -68,13 +124,13 @@ class EvaluatorValue : public std::enable_shared_from_this<EvaluatorValue> { | |
| bool isUnknown() const { return unknown; } | ||
|
|
||
| /// Mark this value as unknown. | ||
| /// This also marks the value as fully evaluated if it isn't already, since | ||
| /// unknown values are considered fully evaluated. This maintains the | ||
| /// invariant that unknown implies fullyEvaluated. | ||
| /// This also marks the value as settled if it isn't already, since unknown | ||
| /// values are considered settled. This maintains the invariant that unknown | ||
| /// implies settled. | ||
| void markUnknown() { | ||
| unknown = true; | ||
| if (!fullyEvaluated) | ||
| markFullyEvaluated(); | ||
| if (!settled) | ||
| markSettled(); | ||
| } | ||
|
|
||
| /// Return the associated MLIR context. | ||
|
|
@@ -100,7 +156,7 @@ class EvaluatorValue : public std::enable_shared_from_this<EvaluatorValue> { | |
| const Kind kind; | ||
| MLIRContext *ctx; | ||
| Location loc; | ||
| bool fullyEvaluated = false; | ||
| bool settled = false; | ||
| bool finalized = false; | ||
| bool unknown = false; | ||
| }; | ||
|
|
@@ -123,24 +179,14 @@ class ReferenceValue : public EvaluatorValue { | |
| EvaluatorValuePtr getValue() const { return value; } | ||
| void setValue(EvaluatorValuePtr newValue) { | ||
| value = std::move(newValue); | ||
| markFullyEvaluated(); | ||
| markSettled(); | ||
| } | ||
|
|
||
| // Finalize the value. | ||
| LogicalResult finalizeImpl(); | ||
|
|
||
| // Return the first non-reference value that is reachable from the reference. | ||
| FailureOr<EvaluatorValuePtr> getStrippedValue() const { | ||
| llvm::SmallPtrSet<ReferenceValue *, 4> visited; | ||
| auto currentValue = value; | ||
| while (auto *v = dyn_cast<ReferenceValue>(currentValue.get())) { | ||
| // Detect a cycle. | ||
| if (!visited.insert(v).second) | ||
| return failure(); | ||
| currentValue = v->getValue(); | ||
| } | ||
| return success(currentValue); | ||
| } | ||
| FailureOr<EvaluatorValuePtr> getStrippedValue() const; | ||
|
|
||
| private: | ||
| EvaluatorValuePtr value; | ||
|
|
@@ -180,7 +226,7 @@ class AttributeValue : public EvaluatorValue { | |
| AttributeValue(PrivateTag, Attribute attr, Location loc) | ||
| : EvaluatorValue(attr.getContext(), Kind::Attr, loc), attr(attr), | ||
| type(cast<TypedAttr>(attr).getType()) { | ||
| markFullyEvaluated(); | ||
| markSettled(); | ||
| } | ||
|
|
||
| // Constructor for partially evaluated AttributeValue | ||
|
|
@@ -215,12 +261,12 @@ class ListValue : public EvaluatorValue { | |
| Location loc) | ||
| : EvaluatorValue(type.getContext(), Kind::List, loc), type(type), | ||
| elements(std::move(elements)) { | ||
| markFullyEvaluated(); | ||
| markSettled(); | ||
| } | ||
|
|
||
| void setElements(SmallVector<EvaluatorValuePtr> newElements) { | ||
| elements = std::move(newElements); | ||
| markFullyEvaluated(); | ||
| markSettled(); | ||
| } | ||
|
|
||
| // Finalize the value. | ||
|
|
@@ -251,7 +297,7 @@ class ObjectValue : public EvaluatorValue { | |
| ObjectValue(om::ClassLike cls, ObjectFields fields, Location loc) | ||
| : EvaluatorValue(cls.getContext(), Kind::Object, loc), cls(cls), | ||
| fields(std::move(fields)) { | ||
| markFullyEvaluated(); | ||
| markSettled(); | ||
| } | ||
|
|
||
| // Partially evaluated value. | ||
|
|
@@ -263,7 +309,7 @@ class ObjectValue : public EvaluatorValue { | |
|
|
||
| void setFields(llvm::SmallDenseMap<StringAttr, EvaluatorValuePtr> newFields) { | ||
| fields = std::move(newFields); | ||
| markFullyEvaluated(); | ||
| markSettled(); | ||
| } | ||
|
|
||
| /// Return the type of the value, which is a ClassType. | ||
|
|
@@ -393,13 +439,13 @@ class Evaluator { | |
| using ObjectKey = std::pair<Value, ActualParameters>; | ||
|
|
||
| private: | ||
| bool isFullyEvaluated(Value value, ActualParameters key) { | ||
| return isFullyEvaluated({value, key}); | ||
| bool isSettled(Value value, ActualParameters key) { | ||
| return isSettled({value, key}); | ||
| } | ||
|
|
||
| bool isFullyEvaluated(ObjectKey key) { | ||
| bool isSettled(ObjectKey key) { | ||
| auto val = objects.lookup(key); | ||
| return val && val->isFullyEvaluated(); | ||
| return val && val->isSettled(); | ||
| } | ||
|
|
||
| FailureOr<EvaluatorValuePtr> | ||
|
|
@@ -410,53 +456,25 @@ class Evaluator { | |
| /// 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<EvaluatorValuePtr> | ||
| evaluator::ResolvedValue | ||
| evaluateValue(Value value, ActualParameters actualParams, Location loc); | ||
|
|
||
| /// Evaluator dispatch functions for the small expression grammar. | ||
| FailureOr<EvaluatorValuePtr> evaluateParameter(BlockArgument formalParam, | ||
| ActualParameters actualParams, | ||
| Location loc); | ||
|
|
||
| FailureOr<EvaluatorValuePtr> | ||
| evaluateConstant(ConstantOp op, ActualParameters actualParams, Location loc); | ||
|
|
||
| FailureOr<EvaluatorValuePtr> | ||
| evaluateIntegerBinaryArithmetic(IntegerBinaryArithmeticOp op, | ||
| ActualParameters actualParams, Location loc); | ||
| evaluator::ResolvedValue evaluateParameter(BlockArgument formalParam, | ||
| ActualParameters actualParams, | ||
| Location loc); | ||
|
|
||
| /// Instantiate an Object with its class name and actual parameters. | ||
| FailureOr<EvaluatorValuePtr> | ||
| evaluateObjectInstance(StringAttr className, ActualParameters actualParams, | ||
| Location loc, ObjectKey instanceKey = {}); | ||
| FailureOr<EvaluatorValuePtr> | ||
| evaluator::ResolvedValue | ||
| evaluateObjectInstance(ObjectOp op, ActualParameters actualParams); | ||
| FailureOr<EvaluatorValuePtr> | ||
| evaluateObjectField(ObjectFieldOp op, ActualParameters actualParams, | ||
| Location loc); | ||
| FailureOr<EvaluatorValuePtr> evaluateListCreate(ListCreateOp op, | ||
| ActualParameters actualParams, | ||
| Location loc); | ||
| FailureOr<EvaluatorValuePtr> evaluateListConcat(ListConcatOp op, | ||
| ActualParameters actualParams, | ||
| Location loc); | ||
| FailureOr<EvaluatorValuePtr> | ||
| evaluateStringConcat(StringConcatOp op, ActualParameters actualParams, | ||
| Location loc); | ||
| FailureOr<EvaluatorValuePtr> | ||
| evaluateBinaryEquality(BinaryEqualityOp op, ActualParameters actualParams, | ||
| Location loc); | ||
| FailureOr<evaluator::EvaluatorValuePtr> | ||
| evaluateBasePathCreate(FrozenBasePathCreateOp op, | ||
| ActualParameters actualParams, Location loc); | ||
| FailureOr<evaluator::EvaluatorValuePtr> | ||
| evaluatePathCreate(FrozenPathCreateOp op, ActualParameters actualParams, | ||
| Location loc); | ||
| FailureOr<evaluator::EvaluatorValuePtr> | ||
| evaluateEmptyPath(FrozenEmptyPathOp op, ActualParameters actualParams, | ||
| Location loc); | ||
| FailureOr<evaluator::EvaluatorValuePtr> | ||
| evaluateUnknownValue(UnknownValueOp op, Location loc); | ||
| evaluator::ResolvedValue evaluateObjectField(ObjectFieldOp op, | ||
| ActualParameters actualParams, | ||
| Location loc); | ||
| evaluator::ResolvedValue evaluateUnknownValue(UnknownValueOp op, | ||
| Location loc); | ||
|
|
||
| LogicalResult evaluatePropertyAssert(PropertyAssertOp op, | ||
| ActualParameters actualParams); | ||
|
|
@@ -477,12 +495,16 @@ class Evaluator { | |
| std::unique_ptr<SmallVector<std::shared_ptr<evaluator::EvaluatorValue>>>> | ||
| actualParametersBuffers; | ||
|
|
||
| /// A worklist that tracks values which needs to be fully evaluated. | ||
| /// A worklist that tracks values that still need more evaluation work. | ||
| std::queue<ObjectKey> worklist; | ||
|
|
||
| /// Evaluator value storage. Return an evaluator value for the given | ||
| /// instantiation context (a pair of Value and parameters). | ||
| DenseMap<ObjectKey, std::shared_ptr<evaluator::EvaluatorValue>> objects; | ||
|
|
||
| /// Tracks object instantiations currently being evaluated so recursive | ||
| /// object graphs reuse the existing placeholder instead of recursing. | ||
| llvm::SmallDenseSet<ObjectKey, 8> activeObjectInstances; | ||
| }; | ||
|
|
||
| /// Helper to enable printing objects in Diagnostics. | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| add_circt_library(CIRCTOMEvaluator | ||
| Evaluator.cpp | ||
| EvaluatorPatterns.cpp | ||
|
|
||
| DEPENDS | ||
| MLIROMIncGen | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not following this... So, if a value is settled and it has a single user, under what circumstances would the mean that the user is not ready? Is this only an accounting issue of the user needs to be updated from pending (it's current state) to ready (it's next state)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's possible under the current complicated state transition, unfortunately mostly because of Referencevalue.
Fox example if reference value point to
add(x, y)reference value itself would be settled when the reference value reachesadd(x, y). Howeveradd(x, y)may not be fully evaluated, soreferecen(add(x, y))is settled but not ready.I think the majority of complexity came from (1) the current in-memory representation about reference value, (2) object field allowing nested fields.
So I'm inclined to pursue the approach to actually mutate the IR, instead of using in-memory representation (#10265 (comment)) since we cannot avoid this complicated scheduling otherwise. This file is the IR I'm experimenting (https://github.com/uenoku/circt/blob/dev/hidetou/evaluator-framework-2/test/Dialect/OM/elaborate-object.mlir) that basically introduces
om.elaborated_objectoperation (https://github.com/uenoku/circt/blob/ebc4122876cb184e193cd7e02631d51b08a6dbcb/include/circt/Dialect/OM/OMOps.td#L209).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of like the IR mutation strategy only because it's simpler and I can therefore understand it (and it doesn't require lots of debugging logging to figure out when it goes wrong). 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think that's better. Though it's necessary to check that the IR mutation approach properly scales for large design, as essentially it flattens entire objects into a single block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, fair. It has to visit every object uniquely. It is possible that the final structure could be very compact, though, as long as unused/unknown structure is discarded.