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

dialects: (arith) make parsing robust with respect to block ordering #1305

Merged
merged 11 commits into from Jul 20, 2023

Conversation

tobiasgrosser
Copy link
Contributor

The function Parse.parse_operand only works, if we are certain that all operands are defined before they are used. As arith can be used in arbitrary control flow graphs, we need to use the robust pattern of Parser.parse_unresolved_operand + Parser.resolve_operands.

Going forward we will remove remaining uses of parse_operand and eventually rename parse_unresolved_operand to parse_operand.

The function Parse.parse_operand only works, if we are certain that all
operands are defined before they are used. As arith can be used in
arbitrary control flow graphs, we need to use the robust pattern of
Parser.parse_unresolved_operand + Parser.resolve_operands.

Going forward we will remove remaining uses of parse_operand and
eventually rename parse_unresolved_operand to parse_operand.
Copy link
Collaborator

@math-fehr math-fehr left a comment

Choose a reason for hiding this comment

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

👍

@tobiasgrosser tobiasgrosser force-pushed the tobiasgrosser/arith_robust_for_cfg branch from e19bdc3 to b982a59 Compare July 20, 2023 06:17
@@ -156,7 +157,7 @@ def test_diagnostic_exception():

opt = xDSLOptMain(args=[filename_in])

with pytest.raises(DiagnosticException):
with pytest.raises(ParseError):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is needed, as the Parser does now the type-checking and now throws an error:

operand is used with type f32, but has been previously used or defined with type i32

Is there a specific reason that we do type-checking in the parser. I feel this is somehow the wrong place to do type checking as it e.g. prevents further type errors at other locations to be emitted.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Let me check if we should instead change the program that is being parsed there.

The reason this is checked at parse time and not after, is because the program cannot even be constructed.
If you refer to a value that is expected to have type i32, and then you parse that value definition which has a different type, let's say i64, then you have a fundamental inconsistency in your program.
It's not even that it is ill-typed, it is that a value has two different types, and thus we should signal it directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am confused. In which way would a type error be fundamentally inconsistent?

Copy link
Collaborator

Choose a reason for hiding this comment

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

What I mean is that you cannot even create an IR that correspond to that program, because you have a value that has two different types.

Also, note that this is different than having an operation not verifying. This is handled later.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, this is exactly how it is done in MLIR, just in case.

@codecov
Copy link

codecov bot commented Jul 20, 2023

Codecov Report

Patch coverage: 100.00% and project coverage change: +0.01 🎉

Comparison is base (fd1bc58) 89.61% compared to head (286c911) 89.62%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1305      +/-   ##
==========================================
+ Coverage   89.61%   89.62%   +0.01%     
==========================================
  Files         187      187              
  Lines       24560    24565       +5     
  Branches     3691     3691              
==========================================
+ Hits        22010    22017       +7     
+ Misses       1968     1967       -1     
+ Partials      582      581       -1     
Impacted Files Coverage Δ
xdsl/dialects/arith.py 96.73% <100.00%> (+0.01%) ⬆️
xdsl/dialects/test.py 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@math-fehr
Copy link
Collaborator

I quickly fixed the test so it still check the same thing as before, which is that a unverified program will return a diagnostic error.

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.

thank you

Copy link
Collaborator

@webmiche webmiche left a comment

Choose a reason for hiding this comment

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

LGTM, modulo using the test dialect.

@@ -0,0 +1,32 @@
// RUN: xdsl-opt %s
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we use the test dialcet here?

Also is this really a property of arith or just a property of the ir-structure in general? If so, we could propably replace everything with the test dialect and move this to a new folder "ir-structure" or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, we can unfortunately not. This test case tests the custom parsing of the arith.add operation, so we need at least arith to stick around. Or are you suggesting I replace the func.func and cf.br operation with the test dialect? I guess that could be possible.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I think the func and cf dialects could certainly go.

Ah yes, I see the changes happen within arith, so that one can't go 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The test.termop does not allow > 0 successors. Are you suggesting we expand termop to allow this?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, that would be needed too, I guess. IMO, you can do this in this PR, I don't mind. It feels like a rather small change...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried this, but the build breaks as I cannot get var_successor_def to work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@webmiche, can you see if you can get the test dialect fixed here?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Fixed it. Let's see what Mathieu thinks about my fixes and then we can merge, I guess.

Copy link
Collaborator

@AntonLydike AntonLydike left a comment

Choose a reason for hiding this comment

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

LGTM, modulo some pyright issues it seems?

@@ -34,7 +36,7 @@ class TestOp(IRDLOperation):


@irdl_op_definition
class TestTermOp(TestOp):
class TestTermOp(IRDLOperation):
Copy link
Collaborator

Choose a reason for hiding this comment

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

@math-fehr it seems that this was broken before. This would only collect the constraints of the parent op for verification and not the ones specified within this class. Do we want it to be that way? Or did I just bandage over an IRDL bug?

Copy link
Collaborator

@compor compor Jul 20, 2023

Choose a reason for hiding this comment

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

I remember doing this, but TBH, I didn't think it might be a potential bug.
My intention was to have TestTermOp be exactly like TestOp plus the terminator trait.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I remember the PR and I think I also thought it was a good idea at the time.

I think this is a problem of decorators and the order they are called in. I think we want this to work on an API level, as it feels very natural (as you say "a test op plus the terminator trait"), but it seems to not work atm. Depending on Mathieu's answer, I would suggest opening an issue or not :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems like a 🐛 to me yes!

Copy link
Collaborator

Choose a reason for hiding this comment

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

opened #1312

@@ -46,6 +48,10 @@ class TestTermOp(TestOp):

name = "test.termop"

res: VarOpResult = var_result_def()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it make sense for a terminator to have results?
This is something we used in some of the tests. That's why my earlier try failed (CI run)

Copy link
Collaborator

@compor compor Jul 20, 2023

Choose a reason for hiding this comment

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

the particular semantics of terminator operations is determined by the specific dialect operations involved

from the MLIR doc.

Looking at the MLIR repo (test suite and lib) there is no terminator with results that I could find.
I'm not sure if there is a point, but it's not explicitly disallowed.

Terminators propagate their operands to their successors unless a value def dominates a block in the region (except when IsolatedFromAbove), in which case it is just accessible.

TL;DR: I think you can, but not sure if it's of any use.

Copy link
Collaborator

@webmiche webmiche Jul 20, 2023

Choose a reason for hiding this comment

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

My question was also about SSAValue scoping which I was not 100% sure about anymore. If SSAValues are block-scoped, this does not make sense as the terminator is the last operation in the block. If they are region-scoped (which from a quick google search, they seem to be in MLIR), I guess you could build IR that uses it.

And then the follow up question is: Will there ever be a use for it? Who would define a terminator that defines an SSAValue?

I guess we can leave it as-is. Better test more behavior than what people actually use. But, it was something that made me raise my eyebrow, so I decided to comment on it 😅

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess you could build IR that uses it.

Yeah, true, I missed that point.
But my doubts remain on the utility of this, and when in Rome, I'd rather do what MLIR does (or does not do in this case :-)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Technically, a terminator can have regions, and even results.
Should they? I don't think so, but maybe someone one day will have a use case for it, so we can keep it as MLIR does.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I added regions as well. Let's keep it like this...

Copy link
Collaborator

@math-fehr math-fehr left a comment

Choose a reason for hiding this comment

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

👍

@@ -46,6 +48,10 @@ class TestTermOp(TestOp):

name = "test.termop"

res: VarOpResult = var_result_def()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Technically, a terminator can have regions, and even results.
Should they? I don't think so, but maybe someone one day will have a use case for it, so we can keep it as MLIR does.

@webmiche
Copy link
Collaborator

I think we can merge after a rebase.

@webmiche webmiche merged commit 3ad7a88 into main Jul 20, 2023
10 checks passed
@webmiche webmiche deleted the tobiasgrosser/arith_robust_for_cfg branch July 20, 2023 19:11
compor added a commit that referenced this pull request Jul 21, 2023
)

This PR performs a minor cleanup in tests (just `test_ir.py`) wrt
changes in the `test` dialect from
#1305:

- Remove helper class `SuccessorOp` since `TestTermOp` accepts
successors now
- Adjust `test` dialect import and use `test.TestOp` instead of `TestOp`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants