This is an archive of the discontinued LLVM Phabricator instance.

[mlir][CSE] Remove duplicated operations with MemRead side-effect
ClosedPublic

Authored by clementval on Mar 31 2022, 5:02 AM.

Details

Summary

This patch enhances the CSE pass to deal with simple cases of duplicated
operations with MemoryEffects.

It allows the CSE pass to remove safely duplicate operations with the
MemoryEffects::Read that have no other side-effecting operations in
between. Other MemoryEffects::Read operation are allowed.

The use case is pretty simple so far so we can build on top of it to add
more features.

This patch is also meant to avoid a dedicated CSE pass in FIR and was
brought together afetr discussion on https://reviews.llvm.org/D112711.
It does not currently cover the full range of use cases described in
https://reviews.llvm.org/D112711 but the idea is to gradually enhance
the MLIR CSE pass to handle common use cases that can be used by
other dialects.

This patch takes advantage of the new CSE capabilities in Fir.

Diff Detail

Event Timeline

clementval created this revision.Mar 31 2022, 5:02 AM
Herald added projects: Restricted Project, Restricted Project. · View Herald Transcript
clementval requested review of this revision.Mar 31 2022, 5:02 AM

Remove unused template

mehdi_amini added inline comments.Mar 31 2022, 3:38 PM
mlir/lib/Transforms/CSE.cpp
148

Can you assert(fromOp->getParent() == toOp->getParent()); ?

151

I likely deserve a TODO: handles effect generically because there could be non-MemoryEffects, River WDYT?

160

Left over debug

204–225
clementval marked 2 inline comments as done.

Address review comments

clementval marked an inline comment as done.Mar 31 2022, 11:39 PM
clementval added inline comments.
mlir/lib/Transforms/CSE.cpp
148

Did you mean assert(fromOp->getBlock() == toOp->getBlock());? Operation does not have getParent() if I checked correctly.

204–225

Good catch! Thanks.

mehdi_amini added inline comments.Mar 31 2022, 11:43 PM
mlir/lib/Transforms/CSE.cpp
148

Right! There is actually a getParent() but it is private to Operation...

217

I wonder if there is an approach to cache some information and avoid a linear traversal of the range, I'm slightly worried we'll encountered some O(n^2) kind of patterns in large block that could make it explode here.

That may be fine to start with the naive approach though.

Thanks for tackling this! Starting to build this out has been a long standing TODO.

typo in description?: remove samfely duplicates

mlir/lib/Transforms/CSE.cpp
138

Can we drop the trivial braces here now?

151

A TODO is fine, though I don't think we want other effects to have an implicit influence on MemoryEffects. Ideally any memory related things that would impact this are specified by MemoryEffects themselves (whether that includes adding additional effects or not remains unknown).

155

Do we care about Allocate and Free here?

217

+1, can we at least have some minimal caching here? LLVM EarlyCSE has a concept of "memory generation" which we could likely implement a simple form of here? (at least until we have better aliasing/analysis infra in MLIR)

clementval edited the summary of this revision. (Show Details)Apr 4 2022, 12:47 AM
clementval updated this revision to Diff 420121.Apr 4 2022, 1:42 AM
clementval marked 5 inline comments as done.

Address some comments

mlir/lib/Transforms/CSE.cpp
151

I updated the TODO here but I guess in this particular case we really care about MemoryEffects only.

155

Not in this case. Just removed.
Maybe the function should be renamed hasWriteOpInBetween() since we just check that now. What do you think?

217

Ok I'll have a look at this and make another update for this patch. Thanks for the pointer.

clementval updated this revision to Diff 420183.Apr 4 2022, 8:01 AM

Update toy tests

Add caching

clementval marked 2 inline comments as done.Apr 5 2022, 12:09 PM
mehdi_amini accepted this revision.Apr 5 2022, 2:54 PM
mehdi_amini added inline comments.
mlir/lib/Transforms/CSE.cpp
147

I think another precondition here now is

assert( isa<MemoryEffectOpInterface>(toOp) && cast<MemoryEffectOpInterface>(toOp).hasEffect<MemoryEffects::Write>()) ?

150

You're hitting twice the map here, one lookup should be enough:

auto memEffectsCacheInfo = memEffectsCache.find(fromOp);
if (memEffectsCacheInfo != memEffectsCache.end()) {
This revision is now accepted and ready to land.Apr 5 2022, 2:54 PM
rriddle requested changes to this revision.Apr 5 2022, 9:23 PM
rriddle added inline comments.
mlir/lib/Transforms/CSE.cpp
63

Can you document this?

65

Can we drop the mlir:: here?

144

Can you move the documentation to the declaration of the function?

150

try_emplace would avoid the additional lookup(via operator[]) further below.

164

If the operation doesn't implement the interface, we need to treat it conservatively (i.e. assume it writes). Can you add a test for this?

266

This is likely worth a comment.

This revision now requires changes to proceed.Apr 5 2022, 9:23 PM
clementval updated this revision to Diff 420810.Apr 6 2022, 5:16 AM
clementval marked 8 inline comments as done.

Address review comments

clementval added inline comments.Apr 6 2022, 9:13 AM
mlir/lib/Transforms/CSE.cpp
147

fromOp and toOp have the MemoryEffects::Read and we are looking for write in between.

rriddle added inline comments.Apr 6 2022, 9:47 AM
mlir/lib/Transforms/CSE.cpp
64–65

operation until MemoryEffects information is valid

Can you tweak this a bit? It's a bit difficult for my head to parse.

211

I think the second part of this is off, the operation could also allocate/write to memory. Should this be !memEffects.onlyHasEffect<...>()`?

clementval marked 2 inline comments as done.

Address review comments

mlir/lib/Transforms/CSE.cpp
211

You are right. We want onlyHasEffect here.

rriddle accepted this revision.Apr 6 2022, 11:52 AM

This seems like a good start, we can beef up the caching/analysis as necessary. Thanks for pushing this! Really awesome to see this being developed.

mlir/test/Transforms/cse.mlir
291–304

Can we trim this a bit? Are all of these necessary?

This revision is now accepted and ready to land.Apr 6 2022, 11:52 AM

Thanks for the review @mehdi_amini @rriddle

mlir/test/Transforms/cse.mlir
291–304

I can trim it a bit before landing.

mehdi_amini added inline comments.Apr 6 2022, 11:58 AM
mlir/lib/Transforms/CSE.cpp
147

Mmmm... OK but then there is something else: if toOp does not have MemoryEffects::Write, it may not be in the map.
But that means that we may jump ahead of it using the map, and then wouldn't it be possible for the loop line 165 to go past the end of the block and crash?
With something like:

read // A - fromOp
...
read // B - toOp
...
write // C

If the first read is in the map, then memEffectsCache.find(fromOp); could return C and so nextOp will be already beyond toOp?

Should we do while (nextOp && nextOp.isBeforeInBlock(toOp)) instead?

clementval added inline comments.Apr 6 2022, 12:09 PM
mlir/lib/Transforms/CSE.cpp
147

We never have write op in the map (at least not with the current code). We just have the information if there is write op between two read operations. fromOp and toOp are always the same kind of operation with the read memory effect.

In the example below in the first call we will look between fromOp 1 and toOp 1. In the second call, without cache we would have to look between fromOp 2 and toOp 2. With the cache we can start directly from toOp 1. So we check between toOp 1 and toOp 2.

read // A - fromOp 1 / fromOp2
...
read // B - toOp 1
...
write // C
...
read // D - toOp 2
mehdi_amini added inline comments.Apr 6 2022, 12:20 PM
mlir/lib/Transforms/CSE.cpp
147

OK, I missed the final update line 179!
There is also an implicit assumption that you call this function with <fromOp 1, topOp1> first and <fromOp 1, topOp2> second right? (I was reading this function in isolation)

150

I don't think you implemented it? River is right that try_emplace here would get you the iterator to use for the updates line 173 and line 179.

clementval added inline comments.Apr 6 2022, 12:20 PM
mlir/lib/Transforms/CSE.cpp
147

My bad. We can have write op in the map but in this case we exit directly.

  1. We check between fromOp1 and toOp 1. We store the write in cache.
  2. We check between fromOp2 and toOp 2. We get the information from the cache that there is write op in between.
read // A - fromOp 1 / fromOp2
...
write // C
...
read // B - toOp 1
...
read // D - toOp 2
clementval added inline comments.Apr 6 2022, 12:23 PM
mlir/lib/Transforms/CSE.cpp
150

Right. Let me update that.

schweitz accepted this revision.Apr 6 2022, 1:07 PM
schweitz added a subscriber: schweitz.

Good to see these improvements going in to MLIR's CSE, Valentin.

clementval updated this revision to Diff 420990.Apr 6 2022, 1:08 PM
  • Use try_emplace
  • Trim test with multiple load
clementval marked 2 inline comments as done.Apr 6 2022, 1:09 PM
mehdi_amini accepted this revision.Apr 6 2022, 4:25 PM

Still LGTM :)