<table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Issue</th>
<td>
<a href=https://github.com/llvm/llvm-project/issues/114727>114727</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>
Missing fold of subtract and comparison by using sign flag on ARM64 and x86-64
</td>
</tr>
<tr>
<th>Labels</th>
<td>
</td>
</tr>
<tr>
<th>Assignees</th>
<td>
</td>
</tr>
<tr>
<th>Reporter</th>
<td>
chandlerc
</td>
</tr>
</table>
<pre>
Full example:
https://cpp.compiler-explorer.com/z/GaPEch7dj
For the code in the example, LLVM generates the following ARM64 assembly:
```
make_id(T*, long):
ldr w9, [x0, x1, lsl #2]
mov w8, #-2097152
asr w9, w9, #9
sub w8, w8, w9
cmn w9, #512, lsl #12
csel w0, w9, w8, gt
ret
```
While I would expect, and GCC generates, something that reuses the `sub`:
```
make_id(T*, long):
ldr w1, [x0, x1, lsl 2]
mov w2, -2097152
asr w1, w1, 9
subs w0, w2, w1
csel w0, w0, w1, pl
ret
```
The same failure also occurs on x86-64, with LLVM generating:
```
make_id(T*, long):
mov ecx, dword ptr [rdi + 4*rsi]
sar ecx, 9
mov eax, -2097152
sub eax, ecx
cmp ecx, -2097151
cmovge eax, ecx
ret
```
While I would expect, and GCC generates:
```
make_id(T*, long):
mov edx, DWORD PTR [rdi+rsi*4]
mov eax, -2097152
sar edx, 9
sub eax, edx
cmovs eax, edx
ret
```
The LLVM IR for x86-64 (should be the same for ARM64):
```llvm
%struct.T = type { i32 }
define dso_local noundef range(i32 -2097151, 4194304) i32 @make_id(T*, long)(ptr nocapture noundef readonly %data, i64 noundef %i) local_unnamed_addr {
entry:
%arrayidx = getelementptr inbounds %struct.T, ptr %data, i64 %i
%bf.load = load i32, ptr %arrayidx, align 4
%shr = ashr i32 %bf.load, 9
%cmp = icmp sgt i32 %shr, -2097152
%sub = sub nuw nsw i32 -2097152, %shr
%retval.0 = select i1 %cmp, i32 %shr, i32 %sub
ret i32 %retval.0
}
```
Note that the `nuw` and `nsw` flags are inferred here due to the `ashr`.
The original C++ code in question:
```cpp
#include <assert.h>
#include <stdint.h>
#include <sys/types.h>
enum E : uint8_t {
V1,
V2,
};
struct T {
E e : sizeof(E) * 8;
bool b : 1;
unsigned payload : 23;
};
constexpr int32_t InvalidId = 0b1111'1111'1110'0000'0000'0000'0000'0000;
int32_t make_id(T* data, ssize_t i) {
uint32_t payload = data[i].payload;
constexpr int shift = 32 - 23;
int32_t ext_id = static_cast<int32_t>(payload << shift) >> shift;
int32_t index = static_cast<uint32_t>(InvalidId) - static_cast<uint32_t>(ext_id);
if (index < 0) {
assert(ext_id > InvalidId);
return ext_id;
}
return index;
}
```
---
An attempt at simplifying the test case produces somewhat bizarre results. Not sure what to make of these.
https://cpp.compiler-explorer.com/z/PGYb7vsGs
Here the C++ code is:
```cpp
#include <assert.h>
#include <stdint.h>
#include <sys/types.h>
constexpr int32_t InvalidId = 0b1111'1111'1110'0000'0000'0000'0000'0000;
int32_t make_id(int32_t* data, ssize_t i) {
int32_t ext_id = data[i];
#ifdef __clang__
__builtin_assume(ext_id <= 0b0000'0000'0011'1111'1111'1111'1111'1111);
#endif
int32_t index = static_cast<uint32_t>(InvalidId) - static_cast<uint32_t>(ext_id);
if (index < 0) {
assert(ext_id > InvalidId);
return ext_id;
}
return index;
}
```
And the LLVM IR (for x86-64) is:
```llvm
define dso_local noundef range(i32 -2097151, 2145386497) i32 @make_id(int*, long)(ptr nocapture noundef readonly %data, i64 noundef %i) local_unnamed_addr {
entry:
%arrayidx = getelementptr inbounds i32, ptr %data, i64 %i
%0 = load i32, ptr %arrayidx, align 4
%cmp = icmp slt i32 %0, 4194304
tail call void @llvm.assume(i1 %cmp)
%cmp1 = icmp sgt i32 %0, -2097152
%sub = sub nuw nsw i32 -2097152, %0
%retval.0 = select i1 %cmp1, i32 %0, i32 %sub
ret i32 %retval.0
}
declare void @llvm.assume(i1 noundef) #1
```
Without the `assume`, LLVM thinks that the `assert` above can fire... Which I think is correct? But weirdly, GCC deletes the assert...
But even setting that aside, I'm a bit surprised at the assume being enough for LLVM to infer both `nuw` and `nsw` on the `sub` here. Because `INT_MIN` seems like it isn't precluded by the assume and yet I feel like the subtract somewhat has to wrap if `%0` is `INT_MIN`. So that seems like it could be a miscompile unless I've misremembered how one of the no-wrap flags works.
And after all of that, LLVM still generates the redundant comparison on bath x86-64 and ARM64:
```
make_id(int*, long):
ldr w9, [x0, x1, lsl #2]
mov w8, #-2097152
sub w8, w8, w9
cmn w9, #512, lsl #12
csel w0, w9, w8, gt
ret
```
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJzcWVtv4zYW_jXMy0EMifL1wQ-Jk8wG6MwWs0GLfTIo8chihyK1JOVLf_2C1MWS46Rz6bbADgaWKJ4bz40fGWat2CnENZndk9nDDatdoc06K5jiEk12k2p-Wj_VUgIeWVlJJMkdiR5IdFc4V1k_ok-EPmVVNcl0WQmJ5haPldQGjf9C6NPvhD59YD8_ZsWC_9YwN79P2oArEDLNEYQK750auoGffvrlI-xQoWEObZjNtZT6INQO7j5_nE-BWYtlKk-9VWQetf_DsGRfcCs4ocsXQu-8UKnVjtBVzwDtP8lNeB5WnorM7o-RfznGgclKIDShZPYw5ir1vuFaBi6a3NJotYhndEzG7FB4q4Imq5bK1ulASvu7GovISjW0jyazmA5Miy80ZhYlHKKzukbqzo3JDLqrfmt-fy2ERHiGg64lBzxWmDkvhSkOHzabc2z8R6tLdIUPjSuYA4O1bYNG5pGtUy_7-6PUhyd-IzzvxCb46SIwfUSCgOb3wuO2Tm0gabxIW8LXbj7TRGdhlfwWT78UCJaVCDkTsjYITFoNOstqY0ErOC7nt_NpkC5cMaoMoXY_nv2drzA7ejJ-0IZD5Yz3tOECCL2HKaF3xopXfrbMDHlXb0hmxythOLt6SOQlXSR_NVTRCulikZV6v8O3uf-sJP_zvMyDoQ-__vPzA_z88rn1MqH33r_0bvpmLr_vxS4QfBSIS-fyV87Ve3tt_o9TNuTh82fItWlTFAhd2iK4MsVQ_U1aa9N07KFberFS7sv2E51ZZ-rMTV6AJA_gThUCWdyDSCiQxcPQAI65UAjc6q3UGZOgdK045mCY2iGhS8_TpwrdwDReTZPIm9CIm0Zvh44uffYrnbHK-XrsZSPjWskTEDrjzDHPI-bTfp7QmfAKgkXbWilWIt8yzo1fRmM4KmfOO5ZnYcawk-DHsOYdOpRYonLeBKFSL9rCwDWhv_jiHNsQdLehJXSW5hOpGQ8yw4tI6ICz0xlyXYqdgumA2RYmMDL_ErzVCxwVOaEzX5yeVPgXu3MduS3MtVz1M3UaOPxT1QdQ9gCDYNFmhwsCehcZdHsmJ1HDiBIzByJu9QcPjLR2ozrt9BrsLetktdnUp9W1NP-kHTY7WruVqfpA5lFoD35kwyiXbGeBGQ9jcjQGORRoEHiN4HTH6p1J5tHksoy0ETuhmIQNofe-1XaA6D81Wie0el0yWVV1FZMIlcmaI5Bk4_GQcZOCJI_Xpq3jQr0zfbKEPvmas0OaNmnrEh6BJHdQC-WWW3dOaIBffIH1A9oPvG-T-6GcJofhZcj9CBgEW_E76pzQ5aMvIULvYNlzA6RaS0gDYTz4XKsAYTlU7NTm-x3Q5Kz2lQmZVtbhsfLF5RK6dfCs9kwK_twUS5TGcRwTujg_IkIXURS9_xhr6WSPewx0BWv9YrcOQrcY-KLu-M7LeWiYZvd-75203wceGK0HbCFyF7h8RQ09Af168ei2ohFtHXMi22bMOpJsWgIfe7o8W7AhyaYRHKxNHkny2I5fCxeK4_GK7HokvHe5l3j7Lmljbdg5zsp8q112qjYQXbgRoCmFnt1bDUOlA2GhOdRGtX4ZzPS9oScJKkfJ9XbvuL29HQ7vFDDnsKwcMAdWlJUU-alBzAgOrYOMWYTKaF5naAOkPvjWk4rfmTEIBm0tnZ3AJ-3A-n0pTDsdsgx07iVZHDWYbzun_fzh3-libz_YoYh_-FbmbRz3pyuI6C9vS39XLXcJ-hUVfaXoBvV81kQTkXsIsd1mkqnddtsK2G7TWkgn1JZZW5c4TOlNs8bLBVys-K3HaqgcFRf5_3Et_1gp3ykeKqBDvIQuz6A3IMor1XDGtd-MVWk8nSXL-XS1uApXhXJ_H2D9WsQ6xpvvINXo-zDqGHjKHt5FQ7DfkjsmJGRMSthrn0DTEJxJX1EDKHmBbeOr4Db6MWgbfSWwjQdYNvpuXNumYCY9QH1z_W1GNNArid87MgtX6NqdYW0QMo_6SztXCPXFjmBzW8IeOad6j5AxBbkwOJlM4NdCZAU8N2wgLGTaGH8MT57gvnZwQGG4PHnx_kTOUWJ3IdjuLJPRlud5cI8KLDrX30gxK3i4V3wmdFECg1SETbQywiKH1tRmMZCi50Ol610RTq_NunSD7yHVrnjzMKDV-NorHAUmcI8Zq234_vzpZfvx-ZOfs4ilBSm-IAgHwipCFw4qg2H745CehmZ5RSd08Aw5omzYwiG7Tp1hmTtDhoJZb-7BsCo0WB-dmQ-k9-_IhAn8SzceGtuSdad4BqWwLWiAWkm0Nvhwj37CYIlliuHIow-gVYdDQOnboL45HB20-WInlz2V5Q4N-LoMTMz1OWSdkPLi-tcgrxVnyhtXVswIq5V3d8pc0V1AeBc1Vw1_eF_zuov-pbfC_6tr3_N95Dfd_N7wdcJXyYrd4DpeJNF8OU9W05tizRcsWi7m83xBl4sVW8WL1TJezdLViqY0x_xGrGlEp3EcTSMaJ_F0gjSOc8anC8rZkk6RTCMsmZCT0HS02d0Ia2tcx_F0QRc3kqUobfc3CLP2VLdpvbO-TQnr7JnPCSdx_VFY6-sz15L7xOnT3wd_kBrpCepA6I-IIQ99trR_OlC8TZmb2sj1GCbvhCvqtMXFYRtvHreV0b-FC8KnsAIPTNtF7Nf0vwEAAP__v-YXkg">