This is an archive of the discontinued LLVM Phabricator instance.

[ObjC][ARC] Prevent moving objc_retain calls past objc_release calls that release the retained object
ClosedPublic

Authored by ahatanak on Jun 25 2021, 2:26 PM.

Details

Summary

This patch fixes what looks like a longstanding bug in ARC optimizer where it reverses the order of objc_retain calls and objc_release calls that retain and release the same object.

The code in ARC optimizer that is responsible for code motion takes the following steps:

  1. Traverse the CFG bottom-up and determine how far up objc_release calls can be moved. Determine the insertion points for the objc_release calls, but don't actually move them.
  2. Traverse the CFG top-down and determine how far down objc_retain calls can be moved. Determine the insertion points for the objc_retain calls, but don't actually move them.
  3. Try to move the objc_retain and objc_release calls if they can't be removed.

The problem is that the insertion points for the objc_retain calls are determined in step 2 without taking into consideration the insertion points for objc_release calls determined in step 1, so the order of an objc_retain call and an objc_release call can be reversed, which is incorrect, even though each step is correct in isolation.

To fix this bug, this patch teaches the top-down traversal step to take into consideration the insertion points for objc_release calls determined in the bottom-up traversal step. Code motion for an objc_retain call is disabled if there is a possibility that it can be moved past an objc_release call that releases the retained object.

rdar://79292791

Diff Detail

Event Timeline

ahatanak created this revision.Jun 25 2021, 2:26 PM
ahatanak requested review of this revision.Jun 25 2021, 2:26 PM
ahatanak updated this revision to Diff 354623.Jun 25 2021, 4:27 PM

Simplify test case.

ahatanak planned changes to this revision.Jun 25 2021, 6:48 PM
ahatanak added inline comments.
llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
1513

This isn't quite correct as it's not impossible for two different retains with different RC identity roots to have the same insertion point.

ahatanak updated this revision to Diff 354642.Jun 25 2021, 8:32 PM

Handle the case where two different retains with different RC identity roots have the same insertion point and add a test case for that.

fhahn added a comment.Jul 1 2021, 2:38 AM

If we move a release past a retain, does this mean the retain/release pair is redundant? If that's the case, would it be easier to detect this case, during or after moving and then remove the redundant pair?

llvm/test/Transforms/ObjCARC/code-motion.ll
43

For this test, would be it be correct to move the retain/release calls to %if.else?

if.else:
  call void @alterRefCount()
  call i8* @llvm.objc.retain(i8* %[[OBJ]])
  call void @use(i8* %obj)
  call void @llvm.objc.release(i8* %[[OBJ]])
  br label %join
51

Given that the function is quite small and the key here is to check precisely *if/where* the calls are moved, it would probably be good to create check lines for the whole function?

You could also pre-commit the tests, so the impact of the change is more obvious in the diff itself.

ahatanak updated this revision to Diff 356055.Jul 1 2021, 5:21 PM
ahatanak marked an inline comment as done.

Rebase after precommitting tests.

The pair should be redundant if the release can be moved to the retain's insertion point. But I don't think removing the pair would be easier than just disabling code motion as doing so would require extra bookkeeping to keep track of the retain/release pairs that can be removed together. I think it's harder to get it right when the control flow isn't as simple as the tests in code-motion.ll.

llvm/test/Transforms/ObjCARC/code-motion.ll
43

It would be correct to move the pair to %if.else, but the retain cannot be moved past the call to @alterRefCount since that could cause obj to be deallocated before the call to @use.

ahatanak updated this revision to Diff 356073.Jul 1 2021, 6:44 PM

Fix test cases.

fhahn accepted this revision.Jul 5 2021, 1:53 AM

LGTM, thanks! It might be good to also add a test case where the insert points are not in the same basic block, ie there’s some control flow between them.

I ’m not super familiar with the code, but @ahatanak was kind enough to walk me through the details offline. The approach looks like a conservative approach to address this miscompile and less risky than trying to undo the bad movements.

I still think undoing the bad movements might be an interesting direction in the future, because it would improve codegen for the examples in the tests.

llvm/lib/Transforms/ObjCARC/ObjCARCOpts.cpp
1501–1536

Might be helpful to document how it is filled?

1517

Identity root*s*?

This revision is now accepted and ready to land.Jul 5 2021, 1:53 AM
ahatanak updated this revision to Diff 356548.Jul 5 2021, 12:06 PM
ahatanak marked 2 inline comments as done.

Rebase after b931c2a714b93327c602ee54e867f812bded4262. Address review comments.

I agree that undoing/preventing the bad move or eliminating the redundant retain/release pair is something we should look into.

This revision was landed with ongoing or failed builds.Jul 5 2021, 12:17 PM
This revision was automatically updated to reflect the committed changes.