Сокращение использования стека во время рекурсии с помощью GCC + ARM

у меня есть рекурсивный парсер спуска для встроенного процессора ARM (в C + GCC, для ARM Cortex M3).

при запуске я заметил, что он использует огромное количество пространства стека (даже больше, чем вы могли бы ожидать), и при ближайшем рассмотрении я обнаружил, что это происходит:

extern int bar(int *p);

int foo() {
 int z = foo(); // it's an example!

 int n[100];  // stack usage
 return z+bar(n); // calling bar(n) stops n from being optimised out
}

результат выполнения arm-none-eabi-gcc-fomit-frame-pointer-s тест.c

foo:
    str lr, [sp, #-4]!  ; Push link register
    sub sp, sp, #412    ; Reserve space on stack, even if we don't need it now!
    bl  foo             ; Recurse
    str r0, [sp, #404]  ; Store result
    ...

Итак, в начале функции, он толкает весь стек рамка на стопку. Однако после нескольких итераций у него есть множество вещей в стеке, которые он еще не использовал.

В идеале, я бы хотел, чтобы GCC генерировал:

foo:
    str lr, [sp, #-4]!  ; Push link register
    ; Don't reserve space, because we don't need it
    bl  foo             ; Recurse
    sub sp, sp, #412    ; Reserve space now
    str r0, [sp, #404]  ; Store result
    ...

(это, вероятно, не правильно, но я надеюсь, что вы получите идею)

что-то вроде этого может быть достигнуто со следующим кодом, но это действительно неприятно (и если GCC inlines fooworker, он снова ломается!). Должен быть лучший способ?

int fooworker(int z) {
 int n[100];  // stack usage
 return z+bar(n); // calling bar(n) stops n from being optimised out
}


int foo() {
 return fooworker(foo());
}

так есть способ говоря GCC только увеличить стек в начале базового блока, или есть оператор "барьер", который заставляет дополнительные push/pop ops добавляться в этот момент? Я предполагаю, что GCC использует один из стандартных типов вызовов ARM - но есть ли способ пометить эти функции другим типом вызова, который немного более эффективен со стеком, или есть способ переписать функции так, чтобы стек использовался немного более разумно?

пожалуйста, не говорите мне не использовать рекурсию, это не отвечаю на вопрос.

2 ответов


int *n = alloca(sizeof(*n) * 100);

это уродливо, и я лично разделил функцию на две части, но, похоже, работает в моем gcc на amd64 на всех уровнях оптимизации.


Это все подвержено оптимизации, но вы также можете попробовать ввести новую область:

extern int bar(int *p);

int foo() {
  int z = foo();

  {
    int n[100];
    return z+bar(n);
  }
}

введение новой области означает, что n не должен жить до foo() называется. Опять же, оптимизация мог бы сломать все это, как ваше собственное решение или принятое.