<table border="1" cellspacing="0" cellpadding="8">
    <tr>
        <th>Issue</th>
        <td>
            <a href=https://github.com/llvm/llvm-project/issues/60391>60391</a>
        </td>
    </tr>

    <tr>
        <th>Summary</th>
        <td>
            SLP Vectorizer issue with memcmp?
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            miscompilation,
            llvm:SLPVectorizer,
            llvm:optimizations
      </td>
    </tr>

    <tr>
      <th>Assignees</th>
      <td>
      </td>
    </tr>

    <tr>
      <th>Reporter</th>
      <td>
          lenary
      </td>
    </tr>
</table>

<pre>
    The following (reduced) C file gives shows some very odd behaviour, which we believe is caused by an issue in the SLP Vectorizer:

```c
extern __attribute__((__nothrow__)) int printf(const char *__restrict fmt, ...)
 __attribute__((__nonnull__(1)));
extern __attribute__((__nothrow__)) int memcmp(const void *s1, const void *s2,
 unsigned long n)
    __attribute__((__nonnull__(1, 2)));

static void initialize_arg(void *obj, unsigned long len) {
 unsigned char *start = obj;
  for (unsigned long i = 0; i < len; ++i) {
    start[i] = i;
  }
}

static void print_arg(const char *prefix, void *arg, unsigned long len) {
  char *start = arg;
 unsigned long i;

  printf("%12s: ", prefix);

  for (i = 0; i < len; ++i)
    printf("0x%02x ", start[i]);

 printf("\n");
}

typedef union {
  __attribute__((aligned(8))) char ret_1[12];
} ret_union_t;

typedef struct {
 __attribute__((aligned(16))) char arg_1[17];
} arg_struct_t;

ret_union_t global_ret;
ret_union_t a_ret;
ret_union_t b_ret;

__attribute__((noinline)) static ret_union_t test(arg_struct_t arg) {
  return global_ret;
}

int main(void) {
  arg_struct_t arg;

  initialize_arg(&arg, sizeof(arg));
  initialize_arg(&global_ret, sizeof(global_ret));

 b_ret = test(arg);
  a_ret = test(arg);

  if (memcmp(&a_ret.ret_1[0], &a_ret.ret_1[0], sizeof(a_ret.ret_1[0])) ||
 memcmp(&a_ret.ret_1[1], &b_ret.ret_1[1], sizeof(a_ret.ret_1[1])) ||
 memcmp(&a_ret.ret_1[2], &b_ret.ret_1[2], sizeof(a_ret.ret_1[2])) ||
 memcmp(&a_ret.ret_1[3], &b_ret.ret_1[3], sizeof(a_ret.ret_1[3])) ||
 memcmp(&a_ret.ret_1[4], &b_ret.ret_1[4], sizeof(a_ret.ret_1[4])) ||
 memcmp(&a_ret.ret_1[5], &b_ret.ret_1[5], sizeof(a_ret.ret_1[5])) ||
 memcmp(&a_ret.ret_1[6], &b_ret.ret_1[6], sizeof(a_ret.ret_1[6])) ||
 memcmp(&a_ret.ret_1[7], &b_ret.ret_1[7], sizeof(a_ret.ret_1[7])) ||
 memcmp(&a_ret.ret_1[8], &b_ret.ret_1[8], sizeof(a_ret.ret_1[8])) ||
 memcmp(&a_ret.ret_1[9], &b_ret.ret_1[9], sizeof(a_ret.ret_1[9])) ||
 memcmp(&a_ret.ret_1[10], &b_ret.ret_1[10], sizeof(a_ret.ret_1[10])) ||
 memcmp(&a_ret.ret_1[11], &b_ret.ret_1[11], sizeof(a_ret.ret_1[11]))) {
 printf("Mismatch between a_ret and b_ret\n");
 print_arg("global_ret", &global_ret, sizeof(global_ret));
 print_arg("a_ret", &a_ret, sizeof(a_ret));
    print_arg("b_ret", &b_ret, sizeof(b_ret));
    printf("Test Failed\n");
    return 1;
  }

  printf("Test Passed\n");
  return 0;
}

```

When compiled with
`clang "-cc1" "-triple" "aarch64_be-arm-none-eabi" "-emit-obj" "-nobuiltininc" "-O2" "-vectorize-slp" "test_c.c" "-o" "test_c.o"` it prints

```
Mismatch between a_ret and b_ret
  global_ret: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 
       a_ret: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 
       b_ret: 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 
Test Failed
```

Which, upon close inspection, is very confusing as the memcmps should only be non-zero if the inspected memory is different, but the memory, once printed, is exactly the same.

This has been reduced from a randomized internal testcase which uses both C and assembly, but the code above reproduces the same problem with pure C. So far, we know it requires:
- Big-endian AArch64
- `-vectorize-slp` must be in the `-cc1` arguments. Without it, this prints "Test Passed".
- `-O2` must be in the `-cc1` arguments. At all other optimisation levels this prints "Test Passed".
- `test` must be noinline, as marked.
- `arg_struct_t` must be more than 16 bytes, so it is passed indirectly on the stack. It apparently also needs to be aligned to 16 bytes as well. It must also be present, even though the test does not look at it for side effects.
- `ret_union_t` must be 8 byte aligned. We cannot reduce its size, because if there are fewer memcmp calls, we get "Test Passed".
- `initialize_arg` must not set everything to zero, and also must not be marked `noinline` (this last requirement is perplexing).

The file does get pass remarks from the SLP Vectorizer with `-Rpass=slp-vectorizer`:
```
test_c.c:53:72: remark: Vectorized horizontal reduction with cost -3 and with tree size 6 [-Rpass=slp-vectorizer]
      memcmp(&a_ret.ret_1[2], &b_ret.ret_1[2], sizeof(a_ret.ret_1[2])) ||
 ^
test_c.c:10:14: remark: Stores SLP vectorized with cost -15 and with tree size 2 [-Rpass=slp-vectorizer]
    start[i] = i;
```

I have also attached a LLVM IR reproducer [test_c.ll.txt](https://github.com/llvm/llvm-project/files/10538869/test_c.ll.txt), which is the same C file, compiled to LLVM IR using `-emit-llvm -disable-llvm-optzns`, which shows the same issues. It can be compiled (after renaming to `test_c.ll`) with `clang "-cc1" "-triple" "aarch64_be-arm-none-eabi" "-emit-obj" "-nobuiltininc" "-O2" "test_c.ll" "-o" "test_c.o" "-vectorize-slp"` to get the equivalent object file.

We haven't reported this nearly as quickly as we would have liked to, because it has been very difficult to reduce into something useful for a bug report, but it shows on `main` just before the clang-16 branch point (that's when the IR file is from), it still shows on `main` this evening, and it is showing in our downstream release branch from September 2022.

We first saw the bug in our downstream compiler, but it reproduces with upstream directly, and we don't have modifications to the SLP vectorizer in our downstream codebase. 
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJy8WV1v6zYP_jXuDZHAlut8XPSiH2-BA5xhw86wcxnINh1rR5YySU6a_voXlGzH-ezpgA0o1NiW-ZAU-ZCSubVirRAfouwpyl7ueOtqbR4kKm72d7ku9w9_1AiVllLvhFpDxBYGy7bAMmJLeIZKSIS12KIFW-udBasbhC2aPeiyhBxrvhW6NRF7hl0tihp2CDlKgVsEYaHgrcUS8j1wBcLaFkEocDXCt6-_wZ9YOG3EO5oofYzilyjux1kc_opwjW8OjYLVijtnRN46XK0itojYYrVS2tVG7-jGkpQWysHGCOWqiC0KrayDouYGIva4Whm0zojCQdU40nk6ndJrHuSaeKVaKf2NJEDQX_r0zzRrsCmazaDZVouSNLMJaXNyj0XsuVOtVX4hS5BarUEddAb4KbWfgZ3rHkbruBNFQBVKOMGleMcVN-uILXpddP4XCTlWQyIpAtH86VTL3uHWceMgSl-ABPS4AJWmx4tjccJPjKP0yf989vLTJ4jYU8SexDEUAHjhUfYkouzFvypGCNH8pTNy-HFmrY-SztDjQNkYrMQbWdw7wM_60AEXDKcX0zMHBYNPVgIOcRsxFrEsYTZKH8FfPEOv1OkCDu782IEH542R4reIZTF764HGjr0Ad6Rk9qz8W6NZxw53-w2WWEGrhFZjV10IWy69dyK2WAzRGlxq0K2SKHtKGOk0gvJPvOyVO1G0R7bOtIUbQd9ETmYn0NysA_T8FJqeBOFn2COtYC11zuXK4GHS-DG_-iQ_ehLGC7orLZQUCjuluwgfC3JoHRk50tfH5XHsGnStUZfUPVlSz2NcqI4fTsScoZxG6xnJRGzWpZcV76iroOoJWV1-b6Ts-PXx7QuUFzzrs-XgmmMw_sGMQamKMm9gdTKF3pz2ARv7HHqGqw8OJl94vAy-faa_AHgdKTkg5RcfXEFKPo3EriGx20js00jpNaT0NlL6aaT7a0j3t5HuP42UXUPKbiNln0aaXUOa3UaafRppfg1pfhtp_mmkxTWkxW2kxaeRlteQlreRlp_P3Phq6n7AEsk_oInrPPERURyY4pjyxw3BL8I23BU15Oh2iKojUq7KrpydNwxHfVjE2Ji8WafpZ3n-VCY_FsfPJPFLQvo-6SAnP5aTn8nJb8npfPQHWgevXEgsL7kDhlKcXGxoL_SKXuJv3NorEjt58bWKPuy3xje_16ig0M2GFIWdcPUwuZDcbxbZpCiSiDH_0xmxkdhdcW6Kena_ynHCTTNRWuEEeS76ydgIN_G7inCtdN4K6YQSqujv_cr6X9t-ozixctPdpLq8KqbDbH18ny6jWQyi2w3aG_Z-GLSdG8et0SPEb3FMQ0IDoyGl4Z6GjIYZDXMaFjQsaeA05DQUNJQ0IA0VHJYfoO8K_2WU_N9FGQf6rTgTRe03VxutoJDaIghlN1g4oRU9EDYcORRaVa0Vag3c-kOEQHP-bKKVJWgl95AjKK0m72g0dWc0rZOGJb2gzZ4ElqKq0KDy6Zu3rpenzZ7uaFVgCBzaFXgV8I0XTu79RMsbnI6N-KMWFmpuIaf46c5QoDK6AQ6Gq1I34h1ph-3QKC59W1lwi92xSWvRQq5dDc8-7iiXm1zux9oVukTgud4iGNwYTRh2UAc2RucSG5-psGkNwvMUvmmoeDieQfih9I4SwuDfrTBoh1OXCTyJ9QRVKbiCx0efuv2TaBaf5N8shqa1jjzdHebQHGKCWUwdf9ugcnYK34WrdetAeB878lBIRTihLMamY7Rf2U9DPDrgUoJ2NRrQGycaYTmFDUjcorQ_j-rb_BHsYVf1TNHWcPMDy_ELR7u_0YuNNgiu5gqSGeR7h9YXCE2OJ108OAhVCoM-nnQw0Dpe_JjCFwd8s-EUmXIPXFoNCrG04DRJ7zaqdNWLJ_V2KKV_12vh38opItB2EY5bJBjdrmuPRuZCqdGC0g6k1j-A00r5swQrSgSsKiycHZs83muPLF54PXrVpvAdoeCK5IY8AOGsr5A-mNGfCXapaRC4Qahwh6bLZii4lLaL2DVtwm4t28mmsNeKwC06MtvsXU2U4TQQJ_j1pAQjHw1zad38CpPMYelnMW3ufAhJboe8odjzS4lmI_FNKNoZnrABhqNT72EygpYdDBKIDbRwfggaMpcC_XeaHqUvVm4OuWeIN9PLFWwohuljlkbp45wRqwc8-jWAlFDTf60cl2F5fLZ45EJbB5PUu8ffcAbRLxzMIMqerqmVvYyLyn-3b4yy_51Zn5CLkvtj6785bdB6b28PjhjZnGSXjGY_a_SNk8iLFe8L1HyLIQS5c7yosQQOX7_--Qt8-f1A7oYU6IyTcurenPfEonZu48mbvUbsdS1c3ebTQjcRe5Vy2_-bbIz-CwsXsVeKRRux1yTO0sVitozY67FY8m5_gi9GNSV8AQhn010j6PSgaKjFFLC-nSNQmJTC8lyiv5rojXtXlswfxIfvCAOC_yhgPXcVXFEiDkgUCJVDAwYVb7oc7qjaq-7lLoe0-Y-70oMa1zvQi90r8YrTnhbIDUQqWy6JVHROC-Z9fsQn39FHjIrYnFhoow01M56YFHJDhcLC360ofoSfO4Sd74h8nEnxw6_bEf26Q7fieyvqh0TRSkeq9bytnPZffAKDtharVvoKwSFv150mfYsiXLe2WtFq-BPCWQx_hSpRhbKI4FdpQsXLcFXUsNFCucCz3EVsbmFXYyiJX34PLCoCZXZRSjhOSHkRzfuEip1n5UD1ofDSbLJCKNCtgVLvlHUGeQMGJVIn1ink2fkbbhw2ORpg8VBxhsWohLEOLN95NckV51K7MDYj94y6Nh-z7aab2_cCvcY7qhxhtf0KNroUlSh8Z-M7gb54HAjpogYl5tziFO7Kh7Rcpkt-hw_JbJ4RD8wXd_XDrMjSOE14UmHM4iRZLNJ0zuYsrbIsy--zO_HAYpbGSRony5hli2nMOGfJLIsXLMvT6j66j7HhQk4p36farO98Rj_M4nSZ3Emeo7T-8yNjjbDBKTw09v77FmOestLHb19_G30PPHkYWrv34AD_3eHlzjx4isnbtY3uYymsswclnHASH07qa_gA6V3fVan09a418uHThBpYK2Kv3sz_BwAA__8uoe9u">