Skip to content

[CIR] Introduce CIR simplification #696

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

Merged
merged 12 commits into from
Aug 7, 2024
Merged

Conversation

gitoleg
Copy link
Collaborator

@gitoleg gitoleg commented Jun 18, 2024

This PR introduce cir simplification pass. The idea is to have a pass for the redundant operations removal/update.

Right now two pattern implemented, both related to the redundant bool operations.
First pattern removes redundant casts from bool to int and back that for some reasons appear in the code.
Second pattern removes sequential unary not operations (!!) .

For example, the code from the test is expanded from the simple check that is common for C code:

#define CHECK_PTR(ptr)  \
  do {                   \
    if (__builtin_expect((!!((ptr) == 0)), 0))\
      return -42; \
  } while(0)

I mark this PR as a draft for the following reasons:

  1. I have no idea if it's useful for the community
  2. There is a test fail - unfortunately current pattern rewriter run DCE underneath the hood and looks like we can't workaround it.
    It's enough just to add an operation to the list - in this case UnaryOp - and call applyOpPatternsAndFold. I could rewrite a test a little in order to make everything non dead or implement a simple fix point algorithm for the particular task (I would do the former).

@bcardosolopes bcardosolopes changed the title [CIR] Itroduce CIR simplification [CIR] Introduce CIR simplification Jun 24, 2024
@bcardosolopes
Copy link
Member

Sorry I didn't had the time to look just yet, but should do that soon, can you update post rebase?

@gitoleg
Copy link
Collaborator Author

gitoleg commented Jul 9, 2024

@bcardosolopes updated!

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

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

This PR introduce cir simplification pass. The idea is to have a pass for the redundant operations removal/update.

Right now two pattern implemented, both related to the redundant bool operations. First pattern removes redundant casts from bool to int and back that for some reasons appear in the code. Second pattern removes sequential unary not operations (!!) .

Neat!

  1. I have no idea if it's useful for the community

Definetely great to get rid of these chains of redundant casts.

  1. There is a test fail - unfortunately current pattern rewriter run DCE underneath the hood and looks like we can't workaround it.

Is this something related to the same issues we see with the canonicalizer? Is it worth adding this case to the bug tracking it?

It's enough just to add an operation to the list - in this case UnaryOp - and call applyOpPatternsAndFold. I could rewrite a test a little in order to make everything non dead or implement a simple fix point algorithm for the particular task (I would do the former).

Former is a fine solution, I'm a bit worried about the overall approach though (more comments inline), I think we should try as much as possible to fix some of these problems at the MLIR level if necessary, I'm afraid those will keep piling up and might accumlate be too much technical debt in the future (e.g. CIR reinventing the wheel to workaround MLIR issues). Thoughts?

@gitoleg gitoleg marked this pull request as ready for review July 16, 2024 10:40
@gitoleg gitoleg requested a review from lanza as a code owner July 16, 2024 10:40
@gitoleg
Copy link
Collaborator Author

gitoleg commented Jul 16, 2024

@bcardosolopes
Frankly, I don't know if it's possible to implement it as fold, since we need to erase operations and return values of different types - I tried and failed :( For example, for the CastOp::fold I returned %6

%7 = cir.cast(bool_to_int, %6 : !cir.bool), !cir.int<s, 32>
%8 = cir.cast(int_to_bool, %7 : !cir.int<s, 32>), !cir.bool

but anyway got a type error. Maybe I'm doing something wrong though and didn't get how I should work wtih folders.

Another thought is that from the future optimizations point of view, I would have CIR simplified as much as we can do it, and better to do it explicit with the pass, than implicit with fold which is called somewhere. Well, maybe I'm wrong.

Is this something related to the same issues we see with the canonicalizer? Is it worth adding this case to the bug tracking it?

I would say it's not a bug, it's a feature!! which may hit us only in tests. It's slightly remind us the case with the canonicalizer since it also run the version of applyPatternsAndFold underneath of the hood. I don't think we need to create another issue here.

To summarize:

  1. I would create a separate pass to handle simplifications. But I still need your advice in case I missed something with folders.
  2. I fixed tests - just add "use" of operations.

@bcardosolopes
Copy link
Member

For example, for the CastOp::fold I returned %6

%7 = cir.cast(bool_to_int, %6 : !cir.bool), !cir.int<s, 32>
%8 = cir.cast(int_to_bool, %7 : !cir.int<s, 32>), !cir.bool

From the canonicalization docs:

/// 3. They can return an existing value or attribute that can be used instead
/// of the operation. The caller will remove the operation and use that
/// result instead.

In this case sounds like it wouldn't be a problem for the folder for cir.cast(int_to_bool, to return %6 directly? The folder would have to look at the defining op for %7 and check it's a bool_to_int. Given the folder expects bool to be returned, looks like %6 would match?

Another thought is that from the future optimizations point of view, I would have CIR simplified as much as we can do it, and better to do it explicit with the pass, than implicit with fold which is called somewhere. Well, maybe I'm wrong.

Simplifications might work better in face of canonicalized IR, but it's a blurry line sometimes where things should live. In the cir.cast(int_to_bool, case, it feels more like canonicalization. But overall if it's doing too much (deleting ops, blocks, adding new ops, etc), I agree with you.

  1. I would create a separate pass to handle simplifications. But I still need your advice in case I missed something with folders.

MergeCleanups is not about folding, turns out that some of the easier things we moved into folders. I'd prefer we rename it to CIRSimplify and add transformations there as well.

My overall feedback is that I want to see the same optimizations you are trying to implement. But my feedback for this PR is that such simplification is doing too much in one place, pieces should be more independent, behave more like traditional
optimizations or canonicalization. For example: removing redundant casts is overall goodness that shouldn't be depending on optimizing unarys.

@gitoleg
Copy link
Collaborator Author

gitoleg commented Jul 23, 2024

@bcardosolopes
Sorry for the delay!

So. I rewrote everything as folders for the correspondent operations and now it looks more elegant and readable than before.
Briefly, what's underneath of the hood:

  1. Implemented simplification of !! for boolean values
  2. Implemented simplification of redundant/unnecessary int/bool cast operations.
  3. Deleted my previous CIRSimplify pass and renamed MergeCleanups to CIRSimplify

The first one is clear. The second one may need some extra clarification.
Basically the approach is the same as it was earlier: collect all the subsequent casts operations, where each cast is one of the following kinds: int_to_bool, bool_to_int and integral. Next, check the head and the tail and do the folding in two cases, when head and tail are:

  1. bool_to_int and int_to_bool: the whole sequence of casts may be dropped as we had boolean value before the first cast
  2. int_to_bool and int_to_bool: the result of the first cast may be used, as no other cast (even the ext cast) won't change the
    boolean result.

Do you need to change the source here because otherwise these operations are removed? Does everything else in the function touching f gets removed or just this one?

Nope, only the results of unary operations that are not stored somewhere. And it's all because of applyOpPatternsAndFold, and UnaryOp that added to the operations to process:

if (isa<BrOp, BrCondOp, ScopeOp, SwitchOp, 
            CastOp, TryOp, UnaryOp>(op))
    ops.push_back(op);
...

if (applyOpPatternsAndFold(ops, std::move(patterns)).failed())
    signalPassFailure();

So looks like O0 and O1 have nothing to do with it. Thus, if I just add BinOp to the list, the expression like a+b will also be removed once there is nothing on the left side.

The opt logic seems not to take into account integer promotion happening with casts here, which could be unsafe.

Well, the patterns I suggest have bool type as a result, so I would say it's safe to drop int promotion here: I explicitly check that the first cast kind is either bool_to_int (i.e. we already have boolean value before the first cast) or int_to_bool (i.e. boolean value is
ready after the first cast).

Depending on the pattern you are trying to get rid off, it might be better to make sure CIRGen gets it right out of the bat.

Well, may be, but it's so easy to do it right here ... The idea is to use just boolean value when we already got it, without any redundant casts.

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

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

Nice! Just some remaining comments/questions

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

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

I think the overall approach is good. Some extra thoughts on how to re-organize the approach a bit.

Ideally we should use one PR for each ::fold'er, since they are not really dependent, just a quick note for next time around.



#define CHECK_PTR(ptr) \
do { \
Copy link
Member

Choose a reason for hiding this comment

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

Do we already have a simplification for do { ... } while (0)? That'd be a good one for a next PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

well, we don't have( maybe I will try to implement. From the other point of view, it kind of convenient to have do .. while(0) since we know where the macroses are) and may use this knowledge for some analysis.

Copy link
Member

@bcardosolopes bcardosolopes Aug 7, 2024

Choose a reason for hiding this comment

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

Good point. Right now we don't have a distinction between folders/simplifications meant for cleaning up versus optimization - MergeCleanups (now CIRSimplify) runs before all other passes (even before lifetime checker), which is still harmless because we don't do anything crazy. Looking fwd we might need to separate things that look more like canonicalization from optimization.

For instance, I could see someone arguing the unary folding you added could hurt analysis.

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

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

LGTM thanks

@bcardosolopes bcardosolopes merged commit b6e5f0d into llvm:main Aug 7, 2024
6 checks passed
Hugobros3 pushed a commit to shady-gang/clangir that referenced this pull request Oct 2, 2024
This PR introduce cir simplification pass. The idea is to have a pass
for the redundant operations removal/update.

Right now two pattern implemented, both related to the redundant `bool`
operations.
First pattern removes redundant casts from `bool` to `int` and back that
for some reasons appear in the code.
Second pattern removes sequential unary not operations (`!!`) .

For example, the code from the test is expanded from the simple check
that is common for C code:

```
#define CHECK_PTR(ptr)  \
  do {                   \
    if (__builtin_expect((!!((ptr) == 0)), 0))\
      return -42; \
  } while(0)
```

I mark this PR as a draft for the following reasons:
1) I have no idea if it's useful for the community
2) There is a test fail - unfortunately current pattern rewriter run DCE
underneath the hood and looks like we can't workaround it.
It's enough just to add an operation to the list - in this case
`UnaryOp` - and call `applyOpPatternsAndFold`. I could rewrite a test a
little in order to make everything non dead or implement a simple fix
point algorithm for the particular task (I would do the former).
smeenai pushed a commit to smeenai/clangir that referenced this pull request Oct 9, 2024
This PR introduce cir simplification pass. The idea is to have a pass
for the redundant operations removal/update.

Right now two pattern implemented, both related to the redundant `bool`
operations.
First pattern removes redundant casts from `bool` to `int` and back that
for some reasons appear in the code.
Second pattern removes sequential unary not operations (`!!`) .

For example, the code from the test is expanded from the simple check
that is common for C code:

```
#define CHECK_PTR(ptr)  \
  do {                   \
    if (__builtin_expect((!!((ptr) == 0)), 0))\
      return -42; \
  } while(0)
```

I mark this PR as a draft for the following reasons:
1) I have no idea if it's useful for the community
2) There is a test fail - unfortunately current pattern rewriter run DCE
underneath the hood and looks like we can't workaround it.
It's enough just to add an operation to the list - in this case
`UnaryOp` - and call `applyOpPatternsAndFold`. I could rewrite a test a
little in order to make everything non dead or implement a simple fix
point algorithm for the particular task (I would do the former).
smeenai pushed a commit to smeenai/clangir that referenced this pull request Oct 9, 2024
This PR introduce cir simplification pass. The idea is to have a pass
for the redundant operations removal/update.

Right now two pattern implemented, both related to the redundant `bool`
operations.
First pattern removes redundant casts from `bool` to `int` and back that
for some reasons appear in the code.
Second pattern removes sequential unary not operations (`!!`) .

For example, the code from the test is expanded from the simple check
that is common for C code:

```
#define CHECK_PTR(ptr)  \
  do {                   \
    if (__builtin_expect((!!((ptr) == 0)), 0))\
      return -42; \
  } while(0)
```

I mark this PR as a draft for the following reasons:
1) I have no idea if it's useful for the community
2) There is a test fail - unfortunately current pattern rewriter run DCE
underneath the hood and looks like we can't workaround it.
It's enough just to add an operation to the list - in this case
`UnaryOp` - and call `applyOpPatternsAndFold`. I could rewrite a test a
little in order to make everything non dead or implement a simple fix
point algorithm for the particular task (I would do the former).
keryell pushed a commit to keryell/clangir that referenced this pull request Oct 19, 2024
This PR introduce cir simplification pass. The idea is to have a pass
for the redundant operations removal/update.

Right now two pattern implemented, both related to the redundant `bool`
operations.
First pattern removes redundant casts from `bool` to `int` and back that
for some reasons appear in the code.
Second pattern removes sequential unary not operations (`!!`) .

For example, the code from the test is expanded from the simple check
that is common for C code:

```
#define CHECK_PTR(ptr)  \
  do {                   \
    if (__builtin_expect((!!((ptr) == 0)), 0))\
      return -42; \
  } while(0)
```

I mark this PR as a draft for the following reasons:
1) I have no idea if it's useful for the community
2) There is a test fail - unfortunately current pattern rewriter run DCE
underneath the hood and looks like we can't workaround it.
It's enough just to add an operation to the list - in this case
`UnaryOp` - and call `applyOpPatternsAndFold`. I could rewrite a test a
little in order to make everything non dead or implement a simple fix
point algorithm for the particular task (I would do the former).
lanza pushed a commit that referenced this pull request Nov 5, 2024
This PR introduce cir simplification pass. The idea is to have a pass
for the redundant operations removal/update.

Right now two pattern implemented, both related to the redundant `bool`
operations.
First pattern removes redundant casts from `bool` to `int` and back that
for some reasons appear in the code.
Second pattern removes sequential unary not operations (`!!`) .

For example, the code from the test is expanded from the simple check
that is common for C code:

```
#define CHECK_PTR(ptr)  \
  do {                   \
    if (__builtin_expect((!!((ptr) == 0)), 0))\
      return -42; \
  } while(0)
```

I mark this PR as a draft for the following reasons:
1) I have no idea if it's useful for the community
2) There is a test fail - unfortunately current pattern rewriter run DCE
underneath the hood and looks like we can't workaround it.
It's enough just to add an operation to the list - in this case
`UnaryOp` - and call `applyOpPatternsAndFold`. I could rewrite a test a
little in order to make everything non dead or implement a simple fix
point algorithm for the particular task (I would do the former).
lanza pushed a commit that referenced this pull request Mar 18, 2025
This PR introduce cir simplification pass. The idea is to have a pass
for the redundant operations removal/update.

Right now two pattern implemented, both related to the redundant `bool`
operations.
First pattern removes redundant casts from `bool` to `int` and back that
for some reasons appear in the code.
Second pattern removes sequential unary not operations (`!!`) .

For example, the code from the test is expanded from the simple check
that is common for C code:

```
#define CHECK_PTR(ptr)  \
  do {                   \
    if (__builtin_expect((!!((ptr) == 0)), 0))\
      return -42; \
  } while(0)
```

I mark this PR as a draft for the following reasons:
1) I have no idea if it's useful for the community
2) There is a test fail - unfortunately current pattern rewriter run DCE
underneath the hood and looks like we can't workaround it.
It's enough just to add an operation to the list - in this case
`UnaryOp` - and call `applyOpPatternsAndFold`. I could rewrite a test a
little in order to make everything non dead or implement a simple fix
point algorithm for the particular task (I would do the former).
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.

2 participants