Как выровнять стек на границе 32 байта в GCC? - PullRequest
8 голосов
/ 12 мая 2011

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

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

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

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

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

Ответы [ 3 ]

15 голосов
/ 17 мая 2011

Я изучал проблему, подал отчет об ошибке 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. К сожалению, я ничего не знаю о его внутренней работе, только начал использовать его на прошлой неделе.

1 голос
/ 23 мая 2017

Я только что столкнулся с той же проблемой с ошибками сегментации при использовании 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));.

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

1 голос
/ 26 апреля 2012

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

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

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

1017 * Е.Г. *

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);
}
...