Является ли эта некорректная генерация кода с массивами значений __m256 ошибкой?

Я сталкиваюсь с ошибкой, вызывающей некорректную генерацию кода с помощью clang 3.4, 3.5 и 3.6 trunk. Источник, который фактически вызвал проблему, довольно сложен, но я смог свести его к самодостаточному примеру:

#include <iostream>
#include <immintrin.h>
#include <string.h>

struct simd_pack
{
enum { num_vectors = 1 };
__m256i _val[num_vectors];
};

simd_pack load_broken(int8_t *p)
{
simd_pack pack;
for (int i = 0; i < simd_pack::num_vectors; ++i) pack._val[i] = _mm256_loadu_si256(reinterpret_cast<__m256i *>(p + i * 32));
return pack;
}

void store_broken(int8_t *p, simd_pack pack)
{
for (int i = 0; i < simd_pack::num_vectors; ++i) _mm256_storeu_si256(reinterpret_cast<__m256i *>(p + i * 32), pack._val[i]);
}

void test_broken(int8_t *out, int8_t *in1, size_t n)
{
size_t i = 0;
for (; i + 31 < n; i += 32)
{
simd_pack p1 = load_broken(in1 + i);
store_broken(out + i, p1);
}
}

int main()
{
int8_t in_buf[256];
int8_t out_buf[256];
for (size_t i = 0; i < 256; ++i) in_buf[i] = i;

test_broken(out_buf, in_buf, 256);
if (memcmp(in_buf, out_buf, 256)) std::cout << "test_broken() failed!" << std::endl;

return 0;
}

Резюме выше: у меня есть простой тип с именем simd_pack который содержит один член, массив из одного __m256i значение. В моем приложении есть операторы и функции, которые принимают эти типы, но проблема может быть проиллюстрирована на примере выше. В частности, test_broken() следует читать из in1 массив, а затем просто скопируйте его значение в out массив. Поэтому призыв к memcmp() в main() должен вернуть ноль. Я компилирую вышеизложенное, используя следующее:

clang++-3.6 bug_test.cc -o bug_test -mavx -O3

Я считаю, что на уровнях оптимизации -O0 а также -O1Тест проходит, пока на уровнях -O2 а также -O3Тест не пройден. Я попытался скомпилировать один и тот же файл с помощью gcc 4.4, 4.6, 4.7 и 4.8, а также Intel C ++ 13.0, и тест прошел на всех уровнях оптимизации.

При более внимательном рассмотрении сгенерированного кода, вот сборка, созданная на уровне оптимизации -O3:

0000000000400a40 <test_broken(signed char*, signed char*, unsigned long)>:
400a40:       55                      push   %rbp
400a41:       48 89 e5                mov    %rsp,%rbp
400a44:       48 81 e4 e0 ff ff ff    and    $0xffffffffffffffe0,%rsp
400a4b:       48 83 ec 40             sub    $0x40,%rsp
400a4f:       48 83 fa 20             cmp    $0x20,%rdx
400a53:       72 2f                   jb     400a84 <test_broken(signed char*, signed char*, unsigned long)+0x44>
400a55:       31 c0                   xor    %eax,%eax
400a57:       66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
400a5e:       00 00
400a60:       c5 fc 10 04 06          vmovups (%rsi,%rax,1),%ymm0
400a65:       c5 f8 29 04 24          vmovaps %xmm0,(%rsp)
400a6a:       c5 fc 28 04 24          vmovaps (%rsp),%ymm0
400a6f:       c5 fc 11 04 07          vmovups %ymm0,(%rdi,%rax,1)
400a74:       48 8d 48 20             lea    0x20(%rax),%rcx
400a78:       48 83 c0 3f             add    $0x3f,%rax
400a7c:       48 39 d0                cmp    %rdx,%rax
400a7f:       48 89 c8                mov    %rcx,%rax
400a82:       72 dc                   jb     400a60 <test_broken(signed char*, signed char*, unsigned long)+0x20>
400a84:       48 89 ec                mov    %rbp,%rsp
400a87:       5d                      pop    %rbp
400a88:       c5 f8 77                vzeroupper
400a8b:       c3                      retq
400a8c:       0f 1f 40 00             nopl   0x0(%rax)

Я воспроизведу ключевую часть для акцента:

  400a60:       c5 fc 10 04 06          vmovups (%rsi,%rax,1),%ymm0
400a65:       c5 f8 29 04 24          vmovaps %xmm0,(%rsp)
400a6a:       c5 fc 28 04 24          vmovaps (%rsp),%ymm0
400a6f:       c5 fc 11 04 07          vmovups %ymm0,(%rdi,%rax,1)

Это отчасти почесывает голову. Сначала загружается 256 бит ymm0 используя ход выравнивания, который я просил, тогда он сохраняет xmm0 (который содержит только младшие 128 бит данных, которые были прочитаны) в стек, а затем сразу считывает 256 бит в ymm0 из местоположения стека, которое было только что записано. Эффект в том, что ymm0Старшие 128 бит (которые записываются в выходной буфер) являются мусором, что приводит к сбою теста.

Есть ли какая-то веская причина, почему это может происходить, кроме просто ошибки компилятора? Я нарушаю какое-то правило, имея simd_pack тип держать массив __m256i ценности? Это, конечно, похоже, связано с этим; если я изменю _val чтобы быть одним значением вместо массива, то сгенерированный код работает как задумано. Тем не менее, мое приложение требует _val быть массивом (его длина зависит от параметра шаблона C ++).

Есть идеи?

11

Решение

Это ошибка в Clang. Тот факт, что это произошло в -O0, является хорошим признаком того, что ошибка находится во внешнем интерфейсе, и в этом случае это темный угол реализации ABI x86-64, связанный с обработкой структуры, содержащей векторный массив точно размер 1!

Ошибка существовала годами, но это первый случай, когда кто-то ударил ее, заметил и сообщил об этом. Спасибо!

http://llvm.org/bugs/show_bug.cgi?id=22563

5

Другие решения


По вопросам рекламы [email protected]