You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Recently we found that when using an empty C++ std::string and call pop_back on the instance. The program abort.
Code:
intmain(int argc, constchar * argv[]) {
std::string str_audioEffect;
str_audioEffect.pop_back(); // llvm/clang will abort; while Apple's will no-op
}
Our internal project may choose to use open-sourced llvm/clang instead of apple's Xcode bundled clang, so any behavior difference is important for us and we need to know the details.
compiler version:
llvm/clang
Apple clang version 13.0.0 (https://github.com/apple/llvm-project.git 8ee3f51668ac68de50d541a815f00859f4922f98)
Target: arm64-apple-darwin22.6.0
Thread model: posix
InstalledDir: /Users/Anonymous/Library/Developer/Toolchains/swift-5.9-RELEASE.xctoolchain/usr/bin/.
apple/clang
Apple clang version 15.0.0 (clang-1500.0.40.1)
Target: arm64-apple-darwin22.6.0
Thread model: posix
InstalledDir: /Applications/Xcode-15.0.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
compiler options (ignore all local paths, demo reproducible project provided in attachment):
When using llvm/clang open-sourced compiler, it will generate brk instruction in binary, so the program abort at the line.
ASM (llvm/clang):
testLibCxx`main:-> 0x100003fa4 <+0>: brk #0x1
apple/clang behavior
When trying to use Apple's clang (from Xcode 15.0.0 bundled xctoolchain), we surprisingly found that the brk is disappear and compiler generate correct call to the pop_back. And does not hit any runtime abort.
From C++ documentation about pop_back, it seems an undefined behavior. So both of this is strictly OK.
If the string is empty, it causes undefined behavior.
Otherwise, the function never throws exceptions (no-throw guarantee).
But I'm still curious about why the generated code is so much different (llvm/clang does not even throw C++ exception, just brk).
Is this strange behavior, just for this specify libcpp code, or more general something bugs/differences between upstream llvm with apple's internal llvm ? I need to knwo what actually happends and provide better correct behavior fixes.
So, I'm trying to use the -mllvm -print-after-all -mllvm -filter-print-funcs='main' to debug between apple/clang and llvm/clang.
IR dump differences
apple/clang
The IR dump shows that apple/clang will do something magic in the CorrelatedValuePropagationPass IR pass.
before CorrelatedValuePropagationPass:
%3 = i80 // This value is from previous basic block
%12 = zexti8%3toi64, !dbg!2274%13 = addi64%12, -1, !dbg!2218%14 = icmpulti64%13, 23, !dbg!2281callvoid@llvm.assume(i1%14), !dbg!2281%15 = trunci64%13toi8, !dbg!2282storei8%15, ptr%2, align1, !dbg!2283brlabel%16
after CorrelatedValuePropagationPass:
%3 = i80 // This value is from previous basic block
%12 = zexti8%3toi64, !dbg!2274%13 = addnswi64%12, -1, !dbg!2218callvoid@llvm.assume(i1true), !dbg!2281%14 = trunci64%13toi8, !dbg!2282storei8%14, ptr%2, align1, !dbg!2283brlabel%15
Note about the add -> add nsw changes, which seems effect the IR semantic.
llvm/clang
However, llvm/clang does not do anything like apple's one in CorrelatedValuePropagationPass pass. It keeps the same IR in and out:
before and after CorrelatedValuePropagationPass:
%8 = i80 // This value is from previous basic block
%9 = icmpslti8%8, 0, !dbg!2204%12 = zexti8%8toi64, !dbg!2205%13 = selecti1%9, i64%11, i64%12, !dbg!2205%14 = addi64%13, -1, !dbg!2206%18 = icmpulti64%14, 23, !dbg!2260callvoid@llvm.assume(i1%18), !dbg!2260%19 = trunci64%14toi8, !dbg!2261storei8%19, ptr%7, align1, !dbg!2262brlabel%20
So, where does the brk (actually, IR unreachable) comes in ?
After some investigate, I found that another pass GVNPass in llvm/clang do the transform. The input IR before GVNPass actually the same as the above one.
before GVNPass:
%8 = i80 // This value is from previous basic block
%9 = icmpslti8%8, 0, !dbg!2204%12 = zexti8%8toi64, !dbg!2205%13 = selecti1%9, i64%11, i64%12, !dbg!2205%14 = addi64%13, -1, !dbg!2206%18 = icmpulti64%14, 23, !dbg!2260tailcallvoid@llvm.assume(i1%18), !dbg!2260%19 = trunci64%14toi8, !dbg!2261storei8%19, ptr%7, align1, !dbg!2262brlabel%20
It's seems by expected. Because the %18 is -1, when using ult (unsigned less than), it will becomes UINT64_MAX, which is not less than 23, so the %18 become false.
ult: interprets the operands as unsigned values and yields true if op1 is less than op2.
For that @llvm.assume(i1, false), it will generate poison instruction, then transform to unreachable by SimplifyCFGPass, and finally lower to brk in arm64.
@llvm.assume The intrinsic allows the optimizer to assume that the provided condition is always true whenever the control flow reaches the intrinsic call. No code is generated for this intrinsic, and instructions that contribute only to the provided condition are not used for code generation. If the condition is violated during execution, the behavior is undefined.
Conclusion
So, from the above investigation result. we can draw a conclusion with the followings:
apple/clang CorrelatedValuePropagationPass seems doing something optimization not correct, which turn a add instruction into add nsw instruction. Can anyone explain the reason and details about this behavior ?
llvm/clang seems do more arbitrary optimization about @llvm.assume. The lower code from libcpp's std::string::pop_back implementation, which use the __builtin_assume
Background
Recently we found that when using an empty C++ std::string and call
pop_back
on the instance. The program abort.Code:
Our internal project may choose to use open-sourced llvm/clang instead of apple's Xcode bundled clang, so any behavior difference is important for us and we need to know the details.
compiler version:
compiler options (ignore all local paths, demo reproducible project provided in attachment):
llvm/clang behavior
When using llvm/clang open-sourced compiler, it will generate
brk
instruction in binary, so the program abort at the line.ASM (llvm/clang):
apple/clang behavior
When trying to use Apple's clang (from Xcode 15.0.0 bundled xctoolchain), we surprisingly found that the
brk
is disappear and compiler generate correct call to thepop_back
. And does not hit any runtime abort.ASM (apple/clang):
Expected behavior
From C++ documentation about pop_back, it seems an undefined behavior. So both of this is strictly OK.
But I'm still curious about why the generated code is so much different (llvm/clang does not even throw C++ exception, just
brk
).Is this strange behavior, just for this specify libcpp code, or more general something bugs/differences between upstream llvm with apple's internal llvm ? I need to knwo what actually happends and provide better correct behavior fixes.
So, I'm trying to use the
-mllvm -print-after-all -mllvm -filter-print-funcs='main'
to debug between apple/clang and llvm/clang.IR dump differences
The IR dump shows that apple/clang will do something magic in the
CorrelatedValuePropagationPass
IR pass.before
CorrelatedValuePropagationPass
:after
CorrelatedValuePropagationPass
:Note about the
add
->add nsw
changes, which seems effect the IR semantic.However, llvm/clang does not do anything like apple's one in
CorrelatedValuePropagationPass
pass. It keeps the same IR in and out:before and after
CorrelatedValuePropagationPass
:So, where does the
brk
(actually, IRunreachable
) comes in ?After some investigate, I found that another pass
GVNPass
in llvm/clang do the transform. The input IR beforeGVNPass
actually the same as the above one.before
GVNPass
:after
GVNPass
:It's seems by expected. Because the
%18
is-1
, when usingult
(unsigned less than), it will becomesUINT64_MAX
, which is not less than23
, so the%18
become false.For that
@llvm.assume(i1, false)
, it will generate poison instruction, then transform tounreachable
bySimplifyCFGPass
, and finally lower tobrk
in arm64.Conclusion
So, from the above investigation result. we can draw a conclusion with the followings:
CorrelatedValuePropagationPass
seems doing something optimization not correct, which turn aadd
instruction intoadd nsw
instruction. Can anyone explain the reason and details about this behavior ?@llvm.assume
. The lower code from libcpp'sstd::string::pop_back
implementation, which use the__builtin_assume
Hope for anyone who are in Apple, or someone who is expert in llvm/clang optimization pass topic for reply. Thanks.
Attachment
testLibCxx.zip
Apple's radar ID: FB13493508
The text was updated successfully, but these errors were encountered: