Что означает `+&` во встроенной ассемблере gcc?

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

У меня вопрос возник, когда я увидел этот пример из авторитетного справочника:

void
dscal (size_t n, double *x, double alpha)
{
  asm ("/* lots of asm here */"
       : "+m" (*(double (*)[n]) x), "+&r" (n), "+b" (x) // <-- There's the "+&r" (n)
       : "d" (alpha), "b" (32), "b" (48), "b" (64),
         "b" (80), "b" (96), "b" (112)
       : "cr0",
         "vs32","vs33","vs34","vs35","vs36","vs37","vs38","vs39",
         "vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47");
}

Что? Почему он рано забивает регистр ввода-вывода? Разве это не тот же регистр?

На этой странице нет никаких объяснений по этому вопросу.

Копнув дальше, я нашел это, в котором говорится:

Операнд, считываемый инструкцией, может быть привязан к операнду раннего удаления, если его использование в качестве входных данных происходит только до записи раннего результата. Добавление альтернатив этой формы часто позволяет GCC создавать более качественный код, когда ранний клоббер может повлиять только на некоторые операнды чтения. См., например, insn «mulsi3» ARM.

Более того, если операнд EarlyClobber также является операндом чтения/записи, то этот операнд записывается только после его использования.

В последнем говорится о случае +&r, но я, честно говоря, не понимаю, о чем там говорится. Я не знаю, что значит «использованный».

Быстрый просмотр grep -r '+&' ядра Linux дал очень мало результатов и только один файл, в котором оно используется в архитектуре x86 (это то, с чем я немного знаком (не слишком)): (файлarch/x86/crypto/curve25519 -x86_64.c)

/* Computes the addition of four-element f1 with value in f2
 * and returns the carry (if any) */
static inline u64 add_scalar(u64 *out, const u64 *f1, u64 f2)
{
    u64 carry_r;

    asm volatile(
        /* Clear registers to propagate the carry bit */
        "  xor %%r8d, %%r8d;"
        "  xor %%r9d, %%r9d;"
        "  xor %%r10d, %%r10d;"
        "  xor %%r11d, %%r11d;"
        "  xor %k1, %k1;"

        /* Begin addition chain */
        "  addq 0(%3), %0;"
        "  movq %0, 0(%2);"
        "  adcxq 8(%3), %%r8;"
        "  movq %%r8, 8(%2);"
        "  adcxq 16(%3), %%r9;"
        "  movq %%r9, 16(%2);"
        "  adcxq 24(%3), %%r10;"
        "  movq %%r10, 24(%2);"

        /* Return the carry bit in a register */
        "  adcx %%r11, %1;"
        : "+&r"(f2), "=&r"(carry_r)
        : "r"(out), "r"(f1)
        : "%r8", "%r9", "%r10", "%r11", "memory", "cc");

    return carry_r;
}

Я действительно не понимаю, почему использования +r будет недостаточно.

Что, если при входе в ассемблер компилятору известно, что оба f2 и f1 содержат одно и то же значение? Может ли он использовать один и тот же регистр для обоих? Это может сработать (таким образом сохраняя регистр), если f1 используется только до записи f2. Но если это невозможно гарантировать, Earlyclobber гарантирует использование отдельных регистров.

David Wohlferd 30.05.2024 04:19

@DavidWohlferd Вот и все! Я ценю ваше время. Я написал несколько надуманных примеров, чтобы создать такую ​​ситуацию, и использование +& изменило ситуацию. Подобные детали кажутся нечеткими и малоизвестными. Для всех, кому интересно, я нашел эту тему по моему вопросу. Кстати, почему бы не ответить на это? Он отлично ответил на мой вопрос!

ChristmasTree 30.05.2024 21:10
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
107
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Поскольку мой комментарий оказался полезным, предлагаю его в качестве ответа:

Что, если при входе в asm компилятор узнает, что f2 и f1 содержат одно и то же значение? Может ли он использовать один и тот же регистр для обоих? Это может сработать (таким образом сохраняя регистр), если f1 используется только до записи f2. Но если это невозможно гарантировать, Earlyclobber гарантирует использование отдельных регистров.

У компилятора есть стимул (производительности) минимизировать использование регистров при вызове asm. Чем больше регистров он использует, тем больше регистров необходимо очистить/восстановить.

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

Это определенно очень круто 😎. И еще, как вы думаете, для разработки ОС следует использовать встроенный ассемблер или просто использовать отдельные файлы?

ChristmasTree 30.05.2024 23:03

Вот пример, если вы хотите добавить его к своему ответу: godbolt.org/z/e9Goe8Eve

Nate Eldredge 30.05.2024 23:18

Разработка ОС сложна. Вы имеете дело с серьёзными проблемами синхронизации, вплоть до тактов и инструкций, которые ни одна другая программа на языке C никогда не будет использовать. Хотя я бы рекомендовал свести к минимуму использование встроенного ассемблера, полностью избегать его может быть непрактично.

David Wohlferd 31.05.2024 02:39

Другие вопросы по теме