В asm volatile встроенные инструкции PTX, зачем также указывать побочные эффекты "памяти"? - PullRequest
3 голосов
/ 29 апреля 2020

Рассмотрим следующую выдержку из руководства Inline PTX Assebly (v10.2):

Компилятор предполагает, что у оператора asm() нет никаких побочных эффектов, кроме изменений выходные операнды. Чтобы гарантировать, что asm не будет удален или перемещен во время генерации PTX, вы должны использовать ключевое слово volatile, например:

asm volatile ("mov.u32 %0, %%clock;" : "=r"(x));

Обычно любая записываемая память будет указана как операнд out, но если есть скрытый побочный эффект на пользовательскую память (например, косвенный доступ к области памяти через операнд), или, если вы хотите остановить любые оптимизации памяти в операторе asm (), выполняемом во время генерации PTX, вы можете добавить " спецификация клобберов памяти после 3-го двоеточия ...

Похоже, что и volatile и :: "memory" предназначены для указания побочных эффектов в памяти. Теперь, конечно, могут быть побочные эффекты, не связанные с памятью (например, для trap;). Но - когда я использовал volatile, разве не бесполезно / бессмысленно также указывать :: "memory")?

слегка связано: При использовании встроенных инструкций asm () PTX, что делает «volatile»?

1 Ответ

5 голосов
/ 29 апреля 2020

Оператор asm, не являющийся volatile, обрабатывается как чистая функция своих входов: выдает один и тот же вывод каждый раз при запуске с одинаковыми явными входами.

И отдельно , без "memory" clobber: не читает и не записывает ничего, что не было упомянуто как входной или выходной операнд.

Звучит так, как предназначены volatile и :: "memory" для обозначения побочных эффектов в памяти.

Нет, volatile просто означает, что выходные операнды не являются чистой функцией входных операндов. "memory" Clobber в основном ортогональный и не , подразумеваемый volatile

Пример, который вы цитировали, похоже, читает счетчик циклов %%clock или что-то, что нужно повторять каждый раз, иначе компилятор может CSE и поднять его из всех oop. Вы не хотели бы, чтобы это заставляло компилятор проливать / перезагружать любые глобальные переменные, которые он имел в регистрах. volatile не подразумевает побочных эффектов памяти, так что это всего лишь билет для этого варианта использования.

Для шаблона asm все равно будет ошибка считывать или записывать любые другие переменные за спиной компилятора ( не через явные "m", "=m" или "+m" операнды), потому что volatile не подразумевает "memory" clobber.

В GNU C inline asm даже "r"(pointer_variable) не не подразумевает, что указанные данные читаются или записываются. Например, присваивание может быть оптимизировано как мертвые хранилища, если все, что вы делаете с переменной, это передаете указатель на нее как входные данные для оператора asm без "memory" Clobber. Как я могу указать, что можно использовать память, * указанную * встроенным аргументом ASM?

A "memory" clobber заставит компилятор предположить, что любая глобально доступная память (или доступная через ввод указателя) могла быть прочитана или записана, и, таким образом, вылить / перезагрузить переменные из регистров вокруг такого оператора asm. (Если escape анализ не может доказать, что ничто иное не может иметь указатель на них, т.е. что указатель на переменную не "ускользнул" от локальной области видимости. Так же, как компиляторы решают, что они могут хранить переменную в регистр для вызова не встроенной функции.)


Так безопасен ли один "memory" без volatile? Нет

A "memory" Clobber не останавливает оптимизацию оператора asm, если не используется ни один из его явных выходных операндов. (Без операндов "= ..." оператор asm является неявно энергозависимым).

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

Не- volatile asm-оператор по-прежнему считается чистой функцией по отношению к. его явные входы и выходы, поэтому asm("..." : "=r"(out) : "r"(in) : "memory"); можно было бы поднять из всех oop, если l oop использовал один и тот же "in" на каждой итерации. (Это может произойти, только если все переменные l oop являются локальными, на которые оператор asm не может иметь указатель (экранирующий анализ, как для вызова не встроенной функции). В противном случае "memory" clobber заблокирует это переупорядочение. )

Или полностью оптимизируется, если все варианты использования "out" могут быть оптимизированы вне зависимости от доступа к памяти вокруг оператора. Решение будет только на основе явных операндов, если вы опустите volatile.

Не так уж много вариантов использования для "memory" клоббера без volatile; Вы можете представить себе его использование для описания функции, которая внутренне использует кеш для запоминания результатов. Компилятор может запускать его так часто или так редко, как ему хочется, и нам на самом деле все равно, мутировал ли внутренний кэш или нет. Это побочный эффект, но не ценный побочный эффект.


( Я предполагаю, что встроенный ассемблер CUDA имеет идентичную семантику встроенного ассемблера GNU C, как это поддерживается / реализовано в Clang / LLVM и G CC. Я действительно ничего не знаю о CUDA, поэтому все, что я сказал выше, основано на встроенном asm GNU C, потому что asm CUDA кажется идентичным. Поправьте меня, если я ошибаюсь, например, если заявления asm без выходных операндов не являются неявно volatile или если CUDA не имеет указателей.

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

...