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
Conversation
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.
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.
👍
e19bdc3
to
b982a59
Compare
tests/xdsl_opt/test_xdsl_opt.py
Outdated
@@ -156,7 +157,7 @@ def test_diagnostic_exception(): | |||
|
|||
opt = xDSLOptMain(args=[filename_in]) | |||
|
|||
with pytest.raises(DiagnosticException): | |||
with pytest.raises(ParseError): |
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.
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.
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.
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.
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 am confused. In which way would a type error be fundamentally inconsistent
?
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.
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.
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.
Also, this is exactly how it is done in MLIR, just in case.
Codecov ReportPatch coverage:
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
☔ View full report in Codecov by Sentry. |
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. |
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.
thank you
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.
LGTM, modulo using the test dialect.
@@ -0,0 +1,32 @@ | |||
// RUN: xdsl-opt %s |
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.
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.
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.
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.
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 the func and cf dialects could certainly go.
Ah yes, I see the changes happen within arith, so that one can't go 👍
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.
The test.termop does not allow > 0
successors. Are you suggesting we expand termop to allow this?
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 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...
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 tried this, but the build breaks as I cannot get var_successor_def to work.
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.
@webmiche, can you see if you can get the test dialect fixed here?
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.
Fixed it. Let's see what Mathieu thinks about my fixes and then we can merge, I guess.
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.
LGTM, modulo some pyright issues it seems?
@@ -34,7 +36,7 @@ class TestOp(IRDLOperation): | |||
|
|||
|
|||
@irdl_op_definition | |||
class TestTermOp(TestOp): | |||
class TestTermOp(IRDLOperation): |
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.
@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?
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 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.
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 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 :)
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.
Seems like a 🐛 to me yes!
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.
opened #1312
@@ -46,6 +48,10 @@ class TestTermOp(TestOp): | |||
|
|||
name = "test.termop" | |||
|
|||
res: VarOpResult = var_result_def() |
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.
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)
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.
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.
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.
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 😅
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 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 :-)
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.
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.
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 added regions as well. Let's keep it like this...
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.
👍
@@ -46,6 +48,10 @@ class TestTermOp(TestOp): | |||
|
|||
name = "test.termop" | |||
|
|||
res: VarOpResult = var_result_def() |
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.
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.
I think we can merge after a rebase. |
) 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`
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.