-
Notifications
You must be signed in to change notification settings - Fork 11.8k
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
InstCombine nonnull annotation and free hoisting interacts badly #51452
Comments
That doesn't sound quite right. If the attribute is on an argument, rather than on the function definition's parameter - then it doesn't seem like an optimization should conclude anything about it being UB to call the function with a null parameter. |
To be clear (since I wasn't in the original description), the issue would arise if g itself gets inlined into some other function (so that other function has a call to free with a nonnull argument), and there are control flow edges which would result in the argument being null. https://reviews.llvm.org/D94180 checks for the combination of nonnull and noundef; in this case, the nonnull is on the actual function argument, and the noundef is on the parameter in the declaration. The intended semantics of passing a null value to a nonnull argument are unclear to me from the LangRef; I'm parsing this as "nonnull + noundef means that a null is UB", but I'm not sure about that.
|
The problem is that the hoisting changed the control condition w/o changing the annotation. No need to inline or derive anything else.
This is clearly a bug in the hoisting but can be resolved by removing the annotation from the call site that is hoisted. |
Thank you! I was concerned that removing the nonnull attribute after hoisting would pessimize cases where the nonnull was also guaranteed by something other than the null check (e.g. attribute((nonnull)), a previous unconditional dereference, etc.). I haven't been able to come up with such a case so far though: marking the function as attribute((nonnull)) retains the nonnull, and adding an unconditional dereference before the null check results in the argument not being marked nonnull regardless of my change. I'll put up that change and we can iterate there if need be. |
You're likely not wrong here. I have seen some other cases by now where code movement in LLVM must drop attributes because they may have been control dependent and as a result code gets worse. If we ever see cases where this makes a big difference then we probably should start having control-dependent and universal attributesin LLVM or something. So you know what can be maintained or what needs to be dropped when hoisting code... |
Though for the record: The solution is here is obviously to first be correct and drop the nonnull attribute. The alternative would be to not perform the optimization when a nonnull is present, but that's likely the worse solution. |
FWIW, |
FYI https://reviews.llvm.org/D104641 added the necessary infrastructure for this, just need to call it. |
|
For poison-implying attributes, we can preserve the attributes in the call site after hoisting if the function call never raises UB. This does not apply to the free function because free can raise UB, however. |
Thanks for the comments and suggestions everyone! I put up https://reviews.llvm.org/D111515 for this. |
*** Bug llvm/llvm-bugzilla-archive#52412 has been marked as a duplicate of this bug. *** |
mentioned in issue llvm/llvm-bugzilla-archive#52412 |
Extended Description
InstCombine attaches a nonnull attribute to a parameter it can prove is non-null, and at -Oz, it also hoists a call to free above a null check if it's profitable for size (3b2db0bcd397). The combination of the two causes problems when we mark a call to free as having a nonnull argument when it's guarded by a null check, and then hoist the free above the null check which was required for the nonnull annotation to be correct. For example, for the following C code:
void free(void *);
void f(void *p) { free(p); }
void g(void *p) { if (p) f(p); }
clang -Oz -S -emit-llvm produces:
define dso_local void @f(i8* nocapture %p) local_unnamed_addr #0 {
entry:
tail call void @free(i8* %p) #2
ret void
}
declare dso_local void @free(i8* nocapture noundef) local_unnamed_addr #1
define dso_local void @g(i8* %p) local_unnamed_addr #0 {
entry:
tail call void @free(i8* nonnull %p) #3
ret void
}
The IR for g retains the nonnull attribute on the argument to free, but the null check that made this attribute be valid is removed. This can then cause problems for callers of g with a null argument (which should have been valid); e.g. SimplifyCFG will remove valid checks after https://reviews.llvm.org/D94180, since it'll consider calling g with null to be UB.
(I can't repro this with C if I call free directly from g; I need the indirection through f to demonstrate the issue. That also matches the structure of our actual code which ran into this issue.)
I can think of a couple of ways to fix this, and I'd appreciate advice on the best approach:
The text was updated successfully, but these errors were encountered: