GCC: запретить использование некоторых регистров - PullRequest
22 голосов
/ 13 июля 2011

Это странная просьба, но у меня есть ощущение, что это возможно. Я хотел бы вставить некоторые прагмы или директивы в области моего кода (написанные на C), чтобы распределитель регистров GCC не использовал их.

Я понимаю, что могу сделать что-то подобное, что может выделить этот регистр для этой переменной

register int var1 asm ("EBX") = 1984;
register int var2 asm ("r9") = 101;

Проблема в том, что я вставляю новые инструкции (для аппаратного симулятора) напрямую, а GCC и GAS их пока не распознают. Мои новые инструкции могут использовать существующие регистры общего назначения, и я хочу убедиться, что некоторые из них (т.е. r12-> r15) зарезервированы.

Сейчас я работаю в макете и хочу быстро провести эксперименты. В будущем я добавлю GAS и добавлю встроенные функции в GCC, но сейчас я ищу быстрое решение.

Спасибо!

Ответы [ 4 ]

15 голосов
/ 13 июля 2011

При написании встроенного ассемблера GCC вы можете указать «список дубликатов» - список регистров, которые могут быть перезаписаны вашим кодом встроенного ассемблера. Затем GCC будет делать все необходимое для сохранения и восстановления данных в этих регистрах (или, во-первых, во избежание их использования) в течение встроенного сегмента asm. Вы также можете привязать входные или выходные регистры к переменным C.

Например:

inline unsigned long addone(unsigned long v)
{
    unsigned long rv;
    asm("mov $1, %%eax;"
        "mov %0, %%ebx;"
        "add %%eax, %%ebx"
        : /* outputs */  "b" (rv)
        : /* inputs */   "g" (v) /* select unused general purpose reg into %0 */
        : /* clobbers */ "eax"
       );
}

Для получения дополнительной информации см. GCC-Inline-Asm-HOWTO .

5 голосов
/ 13 июля 2011

Если вы пишете встроенный блок asm для ваших новых инструкций, есть команды, которые информируют GCC, какие регистры используются этим блоком и как они используются. Затем GCC избегает использования этих регистров или, по крайней мере, сохраняет и перезагружает их содержимое.

5 голосов
/ 13 июля 2011

Если вы используете глобальные явные переменные регистра , они будут зарезервированы во всем модуле компиляции и не будут использоваться компилятором ни для чего другого (он все еще может использоваться системными библиотеками, поэтому выберитечто то будет восстановлено теми).Переменные локального регистра не гарантируют, что ваше значение будет постоянно находиться в регистре, но только в том случае, если на него ссылается код или как операнд asm.

2 голосов

Нестокодированный скретч-регистр во встроенной сборке

Это не прямой ответ на первоначальный вопрос, но с тех пор я продолжаю гуглить это в этом контексте и с тех пор https://stackoverflow.com/a/6683183/895245 принято, я попытаюсь представить возможное улучшение этого ответа.

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

Таким образом, в качестве учебного примера, который бесполезен на практике (может быть сделано в одном lea (%[in1], %[in2]), %[out];), следующий жестко закодированный код регистрационного регистра:

bad.c

#include <assert.h>
#include <inttypes.h>

int main(void) {
    uint64_t in1 = 0xFFFFFFFF;
    uint64_t in2 = 1;
    uint64_t out;
    __asm__ (
        "mov %[in2], %%rax;" /* scratch = in2 */
        "add %[in1], %%rax;" /* scratch += in1 */
        "mov %%rax, %[out];" /* out = scratch */
        : [out] "=r" (out)
        : [in1] "r" (in1),
          [in2] "r" (in2)
        : "rax"
    );
    assert(out == 0x100000000);
}

может скомпилироваться во что-то более эффективное, если вместо этого вы будете использовать эту версию без жесткого кода:

good.c

#include <assert.h>
#include <inttypes.h>

int main(void) {
    uint64_t in1 = 0xFFFFFFFF;
    uint64_t in2 = 1;
    uint64_t out;
    uint64_t scratch;
    __asm__ (
        "mov %[in2], %[scratch];" /* scratch = in2 */
        "add %[in1], %[scratch];" /* scratch += in1 */
        "mov %[scratch], %[out];" /* out = scratch */
        : [scratch] "=&r" (scratch),
          [out] "=r" (out)
        : [in1] "r" (in1),
          [in2] "r" (in2)
        :
    );
    assert(out == 0x100000000);
}

, поскольку компилятор может свободно выбирать любой регистр, который ему нужен, вместо rax,

Обратите внимание, что в этом примере мы должны были пометить скретч как ранний регистр Clobber с помощью &, чтобы сохранитьВместо того, чтобы поместить его в тот же регистр, что и вход, я объяснил это более подробно по адресу: Когда использовать ограничение раннего коббера в расширенной встроенной сборке GCC? Этот пример также не работает в проверенной мной реализациибез &.

Протестировано в Ubuntu 18.10 amd64, GCC 8.2.0, скомпилировано и запущено с:

gcc -O3 -std=c99 -ggdb3 -Wall -Werror -pedantic -o good.out good.c
./good.out

Нестко закодированные чистые регистры также упоминаются в Руководство GCC 6.45.2.6 «Регистры сгустков и царапин», хотя их пример слишком сложен, чтобы простые смертные могли сразу принять:для оператора asm альтернатива состоит в том, чтобы определить переменную и сделать ее выходом раннего клоббера, как в случае с a2 и a3 в примере ниже.Это дает распределителю регистров компилятор больше свободы.Вы также можете определить переменную и сделать ее выводом, привязанным к входу, как с a0 и a1, привязанными соответственно к ap и lda.Конечно, с привязанными выходами ваш ассм не может использовать входное значение после изменения выходного регистра, так как это один и тот же регистр.Более того, если вы опустите ранний клоббер на выходе, возможно, что GCC может выделить тот же регистр для другого из входов, если GCC может доказать, что они имели одинаковое значение при входе в asm.Вот почему a1 имеет ранний клоббер.Можно предположить, что его связанный ввод, lda, имеет значение 16 и без раннего клоббера разделяет тот же регистр, что и% 11.С другой стороны, ap не может быть таким же, как любой другой вход, поэтому ранний клоббер на a0 не нужен.Это также не желательно в этом случае.Ранний клоббер на a0 заставил бы GCC выделить отдельный регистр для входа "m" ( (const double () []) ap).Обратите внимание, что привязка ввода к выходу - это способ установки инициализированного временного регистра, модифицированного оператором asm.GCC предполагает, что вход, не связанный с выходом, остается неизменным, например, «b» (16) ниже устанавливает% 11–16, и GCC может использовать этот регистр в следующем коде, если значение 16 оказалось необходимым.Вы можете даже использовать нормальный вывод asm для нуля, если все входы, которые могут совместно использовать один и тот же регистр, используются до того, как будут использоваться нуля.Регистры VSX, перекрытые оператором asm, могли бы использовать этот метод, за исключением ограничения GCC на количество параметров asm.

static void
dgemv_kernel_4x4 (long n, const double *ap, long lda,
                  const double *x, double *y, double alpha)
{
  double *a0;
  double *a1;
  double *a2;
  double *a3;

  __asm__
    (
     /* lots of asm here */
     "#n=%1 ap=%8=%12 lda=%13 x=%7=%10 y=%0=%2 alpha=%9 o16=%11\n"
     "#a0=%3 a1=%4 a2=%5 a3=%6"
     :
       "+m" (*(double (*)[n]) y),
       "+&r" (n), // 1
       "+b" (y),  // 2
       "=b" (a0), // 3
       "=&b" (a1),    // 4
       "=&b" (a2),    // 5
       "=&b" (a3) // 6
     :
       "m" (*(const double (*)[n]) x),
       "m" (*(const double (*)[]) ap),
       "d" (alpha),   // 9
       "r" (x),       // 10
       "b" (16),  // 11
       "3" (ap),  // 12
       "4" (lda)  // 13
     :
       "cr0",
       "vs32","vs33","vs34","vs35","vs36","vs37",
       "vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47"
     );
}
...