GCC - Как перестроить стек? - PullRequest
7 голосов
/ 04 мая 2010

Я пытаюсь создать приложение, которое использует pthreads и тип __m128 SSE. Согласно руководству GCC, выравнивание стека по умолчанию составляет 16 байтов. Для использования __m128 требуется выравнивание по 16 байтов.

Мой целевой процессор поддерживает SSE. Я использую компилятор GCC, который не поддерживает перестройку стека времени выполнения (например, -mstackrealign). Я не могу использовать любую другую версию компилятора GCC.

Мое тестовое приложение выглядит так:

#include <xmmintrin.h>
#include <pthread.h>
void *f(void *x){
   __m128 y;
   ...
}
int main(void){
  pthread_t p;
  pthread_create(&p, NULL, f, NULL);
}

Приложение генерирует исключение и завершает работу. После простой отладки (printf "% p", & y) я обнаружил, что переменная y не выровнена по 16 байтам.

У меня вопрос: как правильно перестроить стек (16 байт), не используя флаги и атрибуты GCC (они не помогают)? Должен ли я использовать встроенный ассемблер GCC в этой функции потока f ()?

Ответы [ 5 ]

7 голосов
/ 04 мая 2010

Выделите в стеке массив, который на 15 байтов больше, чем sizeof(__m128), и используйте первый выровненный адрес в этом массиве. Если вам нужно несколько, выделите их в массиве с одним 15-байтовым полем для выравнивания.

Я не помню, выделяет ли массив unsigned char защиту от строгой оптимизации псевдонимов компилятором или он работает только наоборот.

#include <stdint.h>

void *f(void *x)
{
   unsigned char y[sizeof(__m128)+15];
   __m128 *py = (__m128*) (((uintptr_t)&y) + 15) & ~(uintptr_t)15);
   ...
}
3 голосов
/ 04 мая 2010

Это не должно происходить в первую очередь, но чтобы обойти проблему, вы можете попробовать:

void *f(void *x)
{
   __m128 y __attribute__ ((aligned (16)));
   ...
}
1 голос
/ 05 мая 2010

Другим решением было бы использование функции заполнения, которая сначала выравнивает стек, а затем вызывает f. Поэтому вместо непосредственного вызова f вы вызываете pad, который сначала дополняет стек, а затем вызывает foo с выровненным стеком.

Код будет выглядеть так:

#include <xmmintrin.h>
#include <pthread.h>

#define ALIGNMENT 16

void *f(void *x) {
    __m128 y;
    // other stuff
}

void * pad(void *val) {
    unsigned int x; // to get the current address from the stack
    unsigned char pad[ALIGNMENT - ((unsigned int) &x) % ALIGNMENT];
    return f(val);
}

int main(void){
    pthread_t p;
    pthread_create(&p, NULL, pad, NULL);
}
0 голосов
/ 15 июня 2017

Извините, что воскресил старую ветку ...

Для тех, кто имеет более новый компилятор, чем OP, OP упоминает опцию -mstackrealign, которая приводит меня к __attribute__((force_align_arg_pointer)). Если ваша функция оптимизирована для использования SSE, но %ebp не выровнена, это сделает прозрачные исправления среды выполнения, если вам это потребуется. Я также узнал, что это проблема только на i386. x86_64 ABI гарантирует, что аргументы выровнены до 16 байтов.

__attribute__((force_align_arg_pointer)) void i_crash_when_not_aligned_to_16_bytes() { ... }

Классная статья для тех, кто хочет узнать больше: http://wiki.osdev.org/System_V_ABI

0 голосов
/ 04 мая 2010

Я решил эту проблему. Вот мое решение:

void another_function(){
   __m128 y;
   ...
}
void *f(void *x){
asm("pushl    %esp");
asm("subl    $16,%esp");
asm("andl    $-0x10,%esp");
another_function();
asm("popl %esp");
}

Во-первых, мы увеличиваем стек на 16 байтов. Во-вторых, мы делаем наименее значимый клев равным 0x0. Мы сохраняем указатель стека, используя операнды push / pop. Мы вызываем другую функцию, у которой все свои локальные переменные выровнены по 16 байтов. Все вложенные функции будут также иметь свои локальные переменные, выровненные по 16 байтов.

И это работает!

...