-
Notifications
You must be signed in to change notification settings - Fork 11.9k
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
LICM sinks/hoist memory accesses to dead stack objects (wrong code at -O1 on x86_64-linux-gnu) #51838
Comments
The bug is either in or exposed by CGP. I'm attaching a slight IR reduction that shows this diff: % llc -o - -disable-cgp=0 52496.ll | clang -x assembler - && ./a.out; echo $? |
I see that CGP is duplicating/sinking casts of alloca'd pointers around lifetime markers and that seems suspicious, but I'm not familiar with how that (or StackColoring?) works. cc'ing some other contributors based on commit logs in those areas. |
The original C reproducer works correctly on current trunk: https://godbolt.org/z/nM3ca1hr4 The IR reproducer from @rotateright still shows a difference with and without codegenprepare, but I think this may be due to the reproducer already having accesses to locations after |
Ok, looks like the original program has an |
Florian, this looks like an address sanitizer bug for 13.0.* at -O1 as the code should be valid:
|
You are right, the access to the out-of-scope stack entry is never executed in the original program! The issue is that As a consequence, the backend will use the same stack slot for
IR reproducer for LICM (https://clang.godbolt.org/z/Pzn5Mf86v):
|
We have this wording in LangRef:
I think we should adjust the semantics here to say that loading from a dead stack object results in a poison value (while storing results in immediate undefined behavior). Otherwise either a) not all unordered, dereferenceable, aligned loads are speculatable or b) allocas are non-dereferenceable after the lifetime ends. I guess b) is viable from a semantics perspective, but not really viable from an implementation perspective right now, because it would make dereferenceability of allocas context-sensitive. @nunoplopes Do you see any issue with making load after alloca lifetime end poison rather than UB? In any case, the store clearly has to be UB, so I guess LICM scalar promotion needs an additional check to not use an unconditional store if it doesn't know that the alloca is live. |
My take: Interpreting lifetime intrinsics as changing whether the memory dereferenceable is a bad idea. An access outside of the lifetime can certainly be defined as returning poison for loads, or leaving the memory in an unspecified state, but allowing them to trap is an extremely profound change to the optimizer which is simply a bad idea. The entire point of an alloca is that it's stack storage with a lifetime the compiler understands. If we allow lifetime intrinsics to change that lifetime, everything which moves code has to be aware of that, and we have a problem which is current context-free which must become context-sensitive. (This is @nikic's option b above. Option a must be rejected out of hand.) I think this is simply a case where the LangRef is out of sync with reality, and we need to fix LangRef. That was my feedback on the change which added this wording to the LangRef, and my position here has not changed. An additional argument in favor of this is that current LangRef assigns different semantics to a lifetime marker on an alloca than a lifetime marker on heap allocated storage. That inconsistency is a strong hint that the specified behavior is wrong. Allowing the distinct disallows e.g. heap to stack conversion without stripping all lifetime markers. Another such hint is that allowing lifetime markers to change dereferenceability implies that an alloca without markers can be more aggressively optimized than one with. That sounds very suspect to say the least. If we do accept my view and change langref, we do have to also go fix stack coloring. Last I looked at that code - it's been a while, so my memory might be off - it took lifetime markers as gospel truth, not a strong hint. With the interpretation I'm suggesting (that the middle end already uses), that becomes a clear bug. |
Some history: lifetime markers were added to allow stack coloring. The semantics was not well documented nor well thought off. Later we (me & Juneyong) tried to improve the description in LangRef. Johannes asked us if we could also define the semantics of lifetime markers for heap objects while at it. We didn't really want that because LLVM doesn't use lifetime markers on heap objects but he insisted that he would use such a feature, so eventually we gave in. The semantics of the lifetime markers for stack objects must be defined by how the stack coloring works. I agree it is never ok to allow stores after liveness ends and that allowing loads beforehand might be ok. |
The core problem with fixing stack coloring is that it makes it effectively impossible to (non-trivially) color escaped allocas. You might have a store to the alloca behind every call or indirect store. StackColoring actually already has an option to gracefully handle cases like in this bug ( llvm-project/llvm/lib/CodeGen/StackColoring.cpp Lines 71 to 80 in 4983fdf
As far as stack coloring is concerned, I'd assume that supporting escaped allocas is the one and only purpose of lifetime markers (otherwise deriving lifetimes from uses shouldn't be particularly hard), so losing support for that would be problematic. |
Extended Description
It appears to be a regression from 12.*.
The text was updated successfully, but these errors were encountered: