Встроенная сборка - принудительно зарегистрировать переменную - PullRequest
0 голосов
/ 27 августа 2018

Мне просто интересно, если я сделаю следующее:

{
    register uint32_t v1 asm("r6"), v2 asm("r7");
    register uint32_t v3 asm("r8"), v4 asm("r10");

    asm volatile (
        /* Move data */
        "   ldm     %[src], {%[v1],%[v2],%[v3],%[v4]};"
        "   stm     %[dst], {%[v1],%[v2],%[v3],%[v4]};"
        : /* output constraints */
          "=m"(*(uint64_t (*)[2])dst),
          [v1]"=&r"(v1), [v2]"=&r"(v2),
          [v3]"=&r"(v3), [v4]"=&r"(v4)
        : /* input constraints */
          "m"(*(const uint64_t (*)[2])src),
          [dst]"r"(read_dst),
          [src]"r"((const uint64_t* )src)
        : /* clobber constraints */
    );
}

гарантированно равен v1 и равен r6, или компилятор может оптимизировать использование другого регистра? Есть ли способ привязать имя к определенному регистру? (без указания вручную %r6 везде?)

Кроме того, есть ли разница между использованием ограничения вывода по сравнению с ограничением Клоббера для временной переменной? (при условии, что на него не ссылаются после вызова встроенной сборки?)

Я использую gcc и clang, поэтому решение должно работать для обоих. Это, конечно, упрощенный пример для размещения вопроса.

1 Ответ

0 голосов
/ 28 августа 2018

Да, это безопасно. Это именно то, что register ... asm("regname"); для , и фактически единственное поддерживаемое использование регистра asm локальных переменных .

На практике gcc сильно предпочтет этот регистр, даже если он требует дополнительных инструкций, если вы продолжите использовать эту переменную позже. ( Использование определенного регистра zmm во встроенном asm ). Но я надеюсь, что если эта переменная мертва, она выделит регистр другим переменным. Тем не менее, вы можете захотеть ограничить область действия этих временных файлов или просто обернуть это в функцию inline.


В asm volatile нет никакой разницы между AFAIK и выходным операндом по сравнению с клоббером. Преимущество выходного операнда состоит в том, что он позволяет компилятору выбирать регистр для вас, но если вы принудительно распределяете регистр вручную, то никакой выгоды в любом случае нет.

Если вы отключите оптимизацию, компилятор фактически зарезервирует место в стеке для локальных пользователей и выльется в них. (А может и нет, поскольку они register переменные?)


Без volatile отсутствие выходных ограничений делает ваш оператор asm неявным образом volatile. Поэтому, если all , выходные ограничения не используются, но вы хотите, чтобы оператор asm запускался в любом случае для какого-либо побочного эффекта с "memory" clobber, вам нужно явно использовать volatile.

В вашем случае вы хотите, чтобы копирование происходило только при использовании вывода памяти. Поэтому вам, вероятно, следует пропустить volatile, чтобы он мог оптимизировать копию до временного , если он может доказать, что ничто не может заботиться о результате. Копия в глобальный или неизвестный указатель не может быть оптимизирована более, чем void foo(int*p) { *p=1; }. Это потенциально видимый побочный эффект функции, которую может наблюдать вызывающая сторона.


Этот сценарий использования для копирования 16 байтов

Это выглядит немного сомнительно. GCC действительно делает код хуже, чем этот, для копирования 16 байтов? Или вы пытаетесь оптимизировать размер по скорости? Обычно вам нужно составить расписание инструкций, чтобы результат загрузки не использовался сразу , особенно для процессоров на заказ (что не редкость в мире ARM).

Поздравляю вас с тем, чтобы все ограничения были правильными. Более 90% вопросов SO о встроенном ассемблере GNU C имеют небезопасные или неоптимальные ограничения, возможно, даже 99%.

Ваши выходные ограничения на раннем этапе и фиктивные "m" операнды ввода / вывода необходимы для того, чтобы это было безопасно.

...