Как выровнять стек по границе 32 байта в GCC?

Я использую сборку MinGW64 на основе GCC 4.6.1 для 64-битной Windows. Я играю с новыми инструкциями Intel AVX. Мои аргументы командной строки -march=corei7-avx -mtune=corei7-avx -mavx.

Но я начал сталкиваться с ошибками сегментации при размещении локальных переменных в стеке. GCC использует выровненные перемещения VMOVAPS и VMOVAPD для перемещения __m256 и __m256d, и эти инструкции требуют 32-байтового выравнивания. Однако стек для 64-битной Windows имеет выравнивание только по 16 байтам.

Как я могу изменить выравнивание стека GCC на 32 байта?

Я пытался использовать -mstackrealign, но безрезультатно, так как это выравнивает только до 16 байтов. Я тоже не мог заставить работать __attribute__((force_align_arg_pointer)), он все равно выравнивается по 16 байтам. Я не смог найти никаких других параметров компилятора, которые могли бы решить эту проблему. Любая помощь приветствуется.

EDIT: я пытался использовать -mpreferred-stack-boundary=5, но GCC говорит, что 5 не поддерживается для этой цели. У меня нет идей.


person Norbert P.    schedule 12.05.2011    source источник
comment
Означает ли это, что __attribute__ ((aligned (32))) тоже не пользуется уважением? например если вы используете __m256 x __attribute__ ((aligned (32)))   -  person nos    schedule 05.06.2014
comment
Linux также не выравнивает стек по 32. gcc, предназначенный для Linux, использует and $-32, %rsp (или любое другое более высокое выравнивание) для выравнивания стека в функциях, которым необходимо передать __m256, __m512 или любые объекты, которые вы объявили с помощью alignas(32) или чего-то большего, чем 16. Это кажется странной ошибкой, которую MinGW gcc не делает. Не используйте ту же последовательность, чтобы сохранить исходный rsp и выровнять его.   -  person Peter Cordes    schedule 07.11.2017


Ответы (3)


Я изучал эту проблему, подал отчет об ошибке GCC и обнаружил, что это проблема, связанная с MinGW64. См. ошибка GCC №49001. Судя по всему, GCC не поддерживает выравнивание стека по 32 байта в Windows. Это эффективно предотвращает использование 256-битных инструкций AVX.

Я исследовал несколько способов решения этой проблемы. Самое простое и прямолинейное решение — заменить выровненный доступ к памяти VMOVAPS/PD/DQA невыровненными альтернативами VMOVUPS и т. д. Итак, прошлой ночью я изучил Python (кстати, очень хороший инструмент) и выполнил следующий скрипт, который выполняет эту работу с входной ассемблерный файл, созданный GCC:

import re
import fileinput
import sys

# fix aligned stack access
# replace aligned vmov* by unaligned vmov* with 32-byte aligned operands 
# see Intel's AVX programming guide, page 39
vmova = re.compile(r"\s*?vmov(\w+).*?((\(%r.*?%ymm)|(%ymm.*?\(%r))")
aligndict = {"aps" : "ups", "apd" : "upd", "dqa" : "dqu"};
for line in fileinput.FileInput(sys.argv[1:],inplace=1):
    m = vmova.match(line)
    if m and m.group(1) in aligndict:
        s = m.group(1)
        print line.replace("vmov"+s, "vmov"+aligndict[s]),
    else:
        print line,

Этот подход довольно безопасен и надежен. Хотя в редких случаях я наблюдал снижение производительности. Когда стек не выровнен, доступ к памяти пересекает границу строки кэша. К счастью, в большинстве случаев код работает так же быстро, как и выровненный доступ. Моя рекомендация: встроенные функции в критических циклах!

Я также попытался исправить распределение стека в прологе каждой функции, используя другой скрипт Python, стараясь всегда выравнивать его по 32-байтовой границе. Кажется, это работает для одного кода, но не для другого. Я должен полагаться на добрую волю GCC, что он будет выделять выровненные локальные переменные (относительно указателя стека), что он обычно и делает. Это не всегда так, особенно при серьезном сбросе регистров из-за необходимости сохранения всего регистра ymm перед вызовом функции. (Все регистры ymm сохраняют вызов). Скрипт могу выложить, если интересно.

Лучшим решением будет исправить сборку GCC MinGW64. К сожалению, я ничего не знаю о его внутренней работе, я только начал использовать его на прошлой неделе.

person Norbert P.    schedule 17.05.2011
comment
Не могли бы вы поделиться своим сценарием перезаписи пролога? Кроме того, как перейти от файла сборки (сгенерированного -S) к исполняемому файлу? Спасибо - person user1649948; 12.09.2015
comment
@НобертП. Улучшилась ли ситуация с более поздними выпусками MinGW64? - person Royi; 08.04.2018
comment
Поскольку GCC, похоже, заметает эту ошибку под ковер (ей уже 6 лет!), мы решили пойти другим путем. Старая добрая петиция, пожалуйста, подпишите ее. change.org/p/gnu-project-gcc- исправление-ошибки-компилятора-54412 - person ichad.c; 27.01.2019

Вы можете получить желаемый эффект,

  1. Объявление ваших переменных не как переменных, а как полей в структуре
  2. Объявление массива, который больше структуры на соответствующее количество отступов
  3. Выполнение арифметики указателя/адреса для поиска 32-байтового выровненного адреса внутри массива
  4. Приведение этого адреса к указателю на вашу структуру
  5. Наконец, используя элементы данных вашей структуры

Вы можете использовать ту же технику, когда malloc() не выравнивает данные в куче должным образом.

E.g.

void foo() {
    struct I_wish_these_were_32B_aligned {
          vec32B foo;
          char bar[32];
    }; // not - no variable definition, just the struct declaration.
    unsigned char a[sizeof(I_wish_these_were_32B_aligned) + 32)];
    unsigned char* a_aligned_to_32B = align_to_32B(a);
    I_wish_these_were_32B_aligned* s = (I_wish_these_were_32B_aligned)a_aligned_to_32B;
    s->foo = ...
}

где

unsigned char* align_to_32B(unsiged char* a) {
     uint64_t u = (unit64_t)a;
     mask_aligned32B = (1 << 5) - 1;
     if (u & mask_aligned32B == 0) return (unsigned char*)u;
     return (unsigned char*)((u|mask_aligned_32B) + 1);
}
person Krazy Glew    schedule 26.04.2012

Я только что столкнулся с той же проблемой, связанной с ошибками сегментации при использовании AVX внутри моих функций. И это также было из-за смещения стека. Учитывая тот факт, что это проблема компилятора (и параметры, которые могут помочь, недоступны в Windows), я работал над использованием стека следующим образом:

  1. Использование статических переменных (см. эту проблему ). Учитывая тот факт, что они не хранятся в стеке, вы можете принудительно выровнять их, используя __attribute__((align(32))) в своем объявлении. Например: static __m256i r __attribute__((aligned(32))).

  2. Встраивание функций/методов, получающих/возвращающих данные AVX. Вы можете заставить GCC встроить вашу функцию/метод, добавив inline и __attribute__((always_inline)) к прототипу/объявлению функции. Встраивание ваших функций увеличивает размер вашей программы, но также не позволяет функции использовать стек (и, следовательно, позволяет избежать проблемы с выравниванием стека). Пример: inline __m256i myAvxFunction(void) __attribute__((always_inline));.

Имейте в виду, что использование статических переменных не является потокобезопасным, как указано в справочнике. Если вы пишете многопоточное приложение, возможно, вам придется добавить некоторую защиту для ваших критических путей.

person Ricardo Alejos    schedule 23.05.2017
comment
В macOS компилятор выравнивает любой массив до 16 байт. Делает ли GCC это также в 64-битной системе? - person Royi; 05.08.2017
comment
Всем привет. Проведя эксперимент на машине Windows 64b с помощью GCC, я обнаружил, что первый элемент массива по умолчанию выравнивается по 16 байтам. Остальные элементы массива выравниваются в зависимости от типа данных элементов массива. Например, массив A из n символов (шириной 1 байт) будет иметь &A[n] = &A[0] + n, будучи &A[n] выровненным по 16 байтам. - person Ricardo Alejos; 07.08.2017
comment
Решают ли более поздние версии MinGW64 с GCC 7.x эту проблему? - person Royi; 08.04.2018