Skip to content
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

core: new inference system for constraints #3456

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

alexarice
Copy link
Collaborator

@alexarice alexarice commented Nov 15, 2024

I've gone through a few different refactors this week and have eventually settled on this slightly smaller refactor for the constraint system. The key addition is that each constraint now returns a dictionary of "resolvers" (name can maybe be improved) which resolve a variable from the given attribute. This allows inference to be decoupled entirely from verification, and inference should no longer be quite as fragile/maybe slightly quicker.

In this version, the resolvers are not used for verification, as I had originally planned, and instead the previous verification framework is left as is. I believe this change can enable the following changes in the future:

PS. as of time of writing it appears this saves 4 lines of code

@alexarice alexarice added the core xDSL core (ir, textual format, ...) label Nov 15, 2024
@alexarice alexarice self-assigned this Nov 15, 2024
Copy link

codecov bot commented Nov 15, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 90.21%. Comparing base (863db56) to head (90d9d10).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3456      +/-   ##
==========================================
+ Coverage   90.20%   90.21%   +0.01%     
==========================================
  Files         459      460       +1     
  Lines       57634    57662      +28     
  Branches     5564     5557       -7     
==========================================
+ Hits        51987    52021      +34     
+ Misses       4201     4198       -3     
+ Partials     1446     1443       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Comment on lines 20 to 25
def print_resolve_type(r: ResolveType | None) -> str:
if isinstance(r, Attribute):
return str(r)
elif isinstance(r, Sequence):
return str(tuple(str(x) for x in r))
return "None"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def print_resolve_type(r: ResolveType | None) -> str:
if isinstance(r, Attribute):
return str(r)
elif isinstance(r, Sequence):
return str(tuple(str(x) for x in r))
return "None"
def _format_resolve_type(r: ResolveType | None) -> str:
if isinstance(r, Attribute):
return str(r)
elif isinstance(r, Sequence):
return str(tuple(str(x) for x in r))
return "None"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also a doc string would be good

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this is dead code so I've removed it

ParseError,
match="Verification error while inferring operation type: ",
VerifyException,
match="i32 should be of base attribute test.param_one",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it conveys the message a bit more directly

Suggested change
match="i32 should be of base attribute test.param_one",
match="unexpected attribute i32, expected test.param_one",

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm actually is this just an existing constraint string? In that case I won't recommend PR creep, it might just be worth changing in the future.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key change here is that the exception happens during verification, rather than at parse time

Copy link
Member

@superlopuh superlopuh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, although I'm still not sure what the motivation for this is. Is it better error messages? Or is there more downstream? Also, my first instinct would be to somehow be a bit more lazy about the resolver situation, would it make sense to make them into lazy model objects, that let the user iterate over the keys, and define each function only once, instead of creating wrappers of wrappers. Not 100% sure if it would work, but might be worth a try in this PR?

alexarice and others added 3 commits November 15, 2024 17:15
Co-authored-by: Sasha Lopoukhine <[email protected]>
Co-authored-by: Sasha Lopoukhine <[email protected]>
Co-authored-by: Sasha Lopoukhine <[email protected]>
@alexarice
Copy link
Collaborator Author

I tried to put some motivation in the PR description and there is a bit more discussion in #3318 about needing to verify properties during parsing with the old system (I have rebased that PR onto this PR).

@superlopuh
Copy link
Member

Thank you, happy to approve once the CI passes

@@ -72,24 +74,23 @@ def verify(
"""
...

def get_resolved_variables(self) -> set[str]:
def get_resolvers(self) -> dict[str, Callable[[AttributeCovT], ResolveType]]:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth making a type alias for this, as it appears in a few places

@alexarice
Copy link
Collaborator Author

Also, my first instinct would be to somehow be a bit more lazy about the resolver situation, would it make sense to make them into lazy model objects, that let the user iterate over the keys, and define each function only once, instead of creating wrappers of wrappers. Not 100% sure if it would work, but might be worth a try in this PR?

I'm also not sure how happy I am about stacks of wrappers. Another option which could be worth trying is to have a resolver be a list of "accessors" where each accessor is a piece of data (such as the attribute name, or operand index). I haven't tried putting this into action so I'm not sure how messy it would get.

Could you expand on what you mean by "lazy model objects"?

@superlopuh
Copy link
Member

superlopuh commented Nov 15, 2024

Something like

class AbstractResolver(abc.ABC):
    @abstractmethod
    def iter_variables() -> Iterator[str]: ...

     @abstractmethod
    def resolve(attr: Attribute) -> Attribute | Sequence[Attribute]:
        ...

@dataclass
class WhateverResolver(AbstractResolver):

    a: AbstractResolver
    b: AbstractResolver

    def iter_variables() -> Iterator[str]:
        yield from a.iter_variables()
        yield from b.iter_variables()

    def resolve(attribute: Attribute) -> Attribute | Sequence[Attribute]:
        return a.resolve(attribute) if creative_condition else b.resolve(attribute)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core xDSL core (ir, textual format, ...)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants