Как заставить компилятор GCC вычислять константы во время компиляции с помощью -Os

Я попытался вычислить хэши для постоянных C-строк во время компиляции с помощью макросов. Это мой пример кода:

#include <stddef.h>
#include <stdint.h>

typedef uint32_t hash_t;

#define hash_cstr(s) ({           \
      typeof(sizeof(s)) i = 0;    \
      hash_t h = 5381;            \
      for (; i < sizeof(s) - 1; ) \
        h = h * 33 + s[i++];      \
      h;                          \
    })

/* tests */
#include <stdio.h>

int main() {
#define test(s) printf("The djb2 hash of " #s " is a %u\n", hash_cstr(#s))

  test(POST);
  test(/path/to/file);
  test(Content-Length);
}

Теперь я запускаю GCC, чтобы показать листинг:

arm-none-eabi-gcc-4.8 -S -O2 -funroll-loops -o hash_test.S hash_test.c

И результат ожидаемый: все строки были удалены и заменены их хэшами. Но обычно я использую -Os для компиляции кода встроенных приложений. Когда я пытаюсь это сделать, у меня есть хэши только для строк, содержащих менее четырех символов. Я также попытался установить параметр max-unroll-times и использовать GCC 4.9:

arm-none-eabi-gcc-4.9 -S -Os -funroll-loops \
  --param max-unroll-times=128 -o hash_test.S hash_test.c

Я не могу понять причину такого поведения и как я могу расширить это ограничение на четыре символа.


person Kayo    schedule 17.12.2015    source источник
comment
Использовать функцию constexpr вместо макроса?   -  person M.M    schedule 17.12.2015
comment
Но constexpr для C++, и пример выглядит как код C   -  person Basile Starynkevitch    schedule 17.12.2015
comment
А также имя файла .c. Это не С++. Зачем меня сюда позвали?   -  person Ivan Aksamentov - Drop    schedule 17.12.2015
comment
@BasileStarynkevitch, но OP прямо сказал, что он использует C ++, упомянув его, а не C в тегах, верно?   -  person Revolver_Ocelot    schedule 17.12.2015
comment
Я предполагаю, что, поскольку код чисто C99, суффикс файла .c, следовательно, ОП хочет на самом деле C (а не C++) и, возможно, неправильно пометил свой вопрос.   -  person Basile Starynkevitch    schedule 17.12.2015
comment
Конечно, я использую чистый C.   -  person Kayo    schedule 17.12.2015
comment
документация GCC по флагам оптимизации заставил меня поверить, что -Os и -O2 включают/отключают только флаги -fxxx. Я пытался скомпилировать со всеми включенными флагами оптимизации и отключать их один за другим, пока не найду подходящую комбинацию около -Os, достаточной для ОП. К моему удивлению, это не сработало: gcc -S -fauto-inc-dec -fcprop-registers -fdce -fdefer-pop ... -fomit-frame-pointer -funroll-loops -o hash_test.S hash_test.c && cat hash_test.S : хеша нет :(   -  person YSC    schedule 18.12.2015
comment
Я реализовал инструмент предварительной обработки, потому что в общем случае мне нужны статические константные хэши. Расширенная оценка во время компиляции является узким местом C.   -  person Kayo    schedule 22.12.2015


Ответы (3)


Я предлагаю поместить соответствующий код в отдельный файл и скомпилировать этот файл с -O2 (не с -Os). Или поместите прагму для конкретной функции, например

 #pragma GCC optimize ("-O2")

перед функцией или используйте атрибут функции, например __attribute__((optimize("02"))) (и атрибут pure, вероятно, также актуален)

Вас может заинтересовать __builtin_constant_p.

Я бы сделал ваш хэш-код некоторой функцией static inline (возможно, с атрибутом функции always_inline), например.

 static inline hash_t hashfun(const char*s) {
    hash_t h = 5381;
    for (const char* p = s; *p; p++) 
      h = h * 33 + *p;
    return h;
 }

Более переносимая (и менее хрупкая) альтернатива состоит в том, чтобы изменить процедуру сборки для создания некоторого файла C (например, с помощью простого сценария awk или python или даже специальной программы C), содержащего такие вещи, как

  const char str1[]="POST";
  hash_t hash1=2089437419; // the hash code of str1

Не забывайте, что файлы .c или .h могут быть сгенерированы чем-то другим (вам просто нужно добавить некоторые правила внутри вашего Makefile, чтобы сгенерировать их); если вашего начальника это беспокоит, покажите ему вики-страницу метапрограммирования.

person Basile Starynkevitch    schedule 17.12.2015
comment
Да, я использую __builtin_constant_p в реальном коде, но это упрощенный пример для проверки возможности вычисления хэша во время компиляции. - person Kayo; 17.12.2015
comment
Функция static inline на самом деле не вычисляется во время компиляции с GCC ‹ 4.9. - person Kayo; 17.12.2015

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

#define _hash_cstr_4(s, o)                \
  for (; i < ((o + 4) < sizeof(s) - 1 ?   \
              (o + 4) : sizeof(s) - 1); ) \
    h = h * 33 + s[i++]

#define _hash_cstr_16(s, o)           \
  _hash_cstr_4(s, o);                 \
  _hash_cstr_4(s, o + 4);             \
  _hash_cstr_4(s, o + 8);             \
  _hash_cstr_4(s, o + 12)

#define _hash_cstr_64(s, o)           \
  _hash_cstr_16(s, o);                \
  _hash_cstr_16(s, o + 16);           \
  _hash_cstr_16(s, o + 32);           \
  _hash_cstr_16(s, o + 48)

#define _hash_cstr_256(s, o)          \
  _hash_cstr_64(s, o);                \
  _hash_cstr_64(s, o + 64);           \
  _hash_cstr_64(s, o + 128);          \
  _hash_cstr_64(s, o + 192)

#define hash_cstr(s) ({                  \
      typeof(sizeof(s)) i = 0;           \
      hash_t h = 5381;                   \
      if (sizeof(s) - 1 < 256) {         \
        _hash_cstr_256(s, 0);            \
      } else                             \
        for (; i < sizeof(s) - 1; )      \
          h = h * 33 + s[i++];           \
      h;                                 \
    })

Если длина хешированной строки меньше 256 символов, хэш вычисляется во время компиляции, в противном случае хэш вычисляется во время выполнения.

Это решение не требует дополнительной настройки компилятора. Он также работает с -Os и -O1.

person Kayo    schedule 17.12.2015

Если С++ разрешено, дайте возможность функции шаблона, например:

template<int I>
  hash_t hash_rec(const char* str, hash_t h) {
  if( I > 0 ) {
    return hash_rec<I-1>(str, h * 33 + str[I-1]);
  } else {
    return h;
  }
}

#define hash(str) hash_rec<sizeof(str)>(str, 5381)

h = hash(str);
person ufok    schedule 17.12.2015
comment
Конечно, я знаю такие вещи, но я избегаю использования C++ в пользу чистого C. - person Kayo; 17.12.2015