В x86-64 Tour of Intel Manuals я прочитал
Возможно, самым удивительным фактом является то, что такая инструкция, как
MOV EAX, EBX
автоматически обнуляет старшие 32 битаRAX
регистра.
В документации Intel (3.4.1.1 Регистры общего назначения в 64-битном режиме в ручной базовой архитектуре), цитируемой в том же источнике, говорится:
- 64-битные операнды генерируют 64-битный результат в регистре общего назначения назначения.
- 32-битные операнды генерируют 32-битный результат с расширением нулями до 64-битного результата в целевом регистре общего назначения.
- 8-битные и 16-битные операнды генерируют 8-битный или 16-битный результат. Старшие 56 бит или 48 бит (соответственно) регистра назначения общего назначения не изменяются в ходе операции. Если результат 8-битной или 16-битной операции предназначен для вычисления 64-битного адреса, явно расширьте регистр знаками до полных 64 бит.
В сборках x86-32 и x86-64 16-битные инструкции, такие как
mov ax, bx
не показывайте такого "странного" поведения, когда верхнее слово eax обнуляется.
Таким образом: какова причина, по которой было введено это поведение? На первый взгляд это кажется нелогичным (но, возможно, причина в том, что я привык к особенностям сборки x86-32).
r32
операндом назначения обнуляют высокий 32, а не объединяются. Например, некоторые ассемблеры заменят егоpmovmskb r64, xmm
наpmovmskb r32, xmm
, сохранив REX, потому что 64-битная конечная версия ведет себя идентично. Несмотря на то, что в разделе «Работа» руководства перечислены все 6 комбинаций 32/64-битного источника и 64/128/256-битного источника отдельно, неявное нулевое расширение формы r32 дублирует явное нулевое расширение формы r64. Мне любопытна реализация HW ...xor eax,eax
илиxor r8d,r8d
- лучший способ обнулить RAX или R8 (сохранение префикса REX для RAX, а 64-битный XOR даже не обрабатывается специально в Silvermont). Связанный: Как именно работают частичные регистры на Haswell / Skylake? Написание AL, кажется, имеет ложную зависимость от RAX, а AH непоследователенОтветы:
Я не AMD и не говорю за них, но я бы поступил так же. Поскольку обнуление верхней половины не создает зависимости от предыдущего значения, ЦП должен ждать. Механизм переименования регистров , по сути, потерпел бы поражение, если бы это не было сделано таким образом.
Таким образом, вы можете писать быстрый код, используя 32-битные значения в 64-битном режиме, без необходимости постоянно явно нарушать зависимости. Без этого поведения каждой 32-битной инструкции в 64-битном режиме пришлось бы ждать чего-то, что происходило раньше, даже если эта высокая часть почти никогда не будет использоваться. (Создание
int
64-битной версии приведет к потере места в кэше и пропускной способности памяти; x86-64 наиболее эффективно поддерживает 32- и 64-битные размеры операндов )Странное поведение для 8- и 16-битных операндов. Безумие зависимости - одна из причин, по которой теперь избегают 16-битных инструкций. x86-64 унаследовал это от 8086 для 8-битных и 386 для 16-битных, и решил, что 8- и 16-битные регистры работают в 64-битном режиме так же, как и в 32-битном режиме.
См. Также Почему GCC не использует частичные регистры? для практических деталей того, как записи в 8- и 16-битные частичные регистры (и последующие чтения из полного регистра) обрабатываются реальными процессорами.
источник
Это просто экономит место в инструкциях и наборе инструкций. Вы можете сразу же переместить небольшие значения в 64-битный регистр, используя существующие (32-битные) инструкции.
Это также избавляет вас от необходимости кодировать 8-байтовые значения
MOV RAX, 42
, когдаMOV EAX, 42
их можно использовать повторно.Эта оптимизация не так важна для 8- и 16-битных операций (потому что они меньше), и изменение правил там также нарушит старый код.
источник
XOR EAX, EAX
потомуXOR RAX, RAX
что потребуется префикс REX.[rsi + edx]
не допускается). Конечно, еще одна важная причина - избегать ложных зависимостей / частичных остановок регистрации (другой ответ).Без расширения нуля до 64 битов это будет означать, что инструкция, из
rax
которой выполняется чтение, будет иметь 2 зависимости для своегоrax
операнда (инструкция, которая выполняет запись,eax
и инструкция, которая записываетrax
до нее), это означает, что 1) ROB должен иметь записи для множественные зависимости для одного операнда, что означает, что ROB потребует больше логики и транзисторов и займет больше места, а выполнение будет медленнее в ожидании ненужной второй зависимости, выполнение которой может занять много времени; или, как вариант 2), что, как я предполагаю, происходит с 16-битными инструкциями, этап распределения, вероятно, останавливается (т.е. если RAT имеет активное выделение дляax
записи иeax
появляется чтение, он останавливается до тех пор, покаax
запись не прекратится).Единственное преимущество ненулевого расширения - это обеспечение включения битов более высокого порядка
rax
, например, если он изначально содержит 0xffffffffffffffff, результатом будет 0xffffffff00000007, но у ISA очень мало причин давать эту гарантию такой ценой, и более вероятно, что преимущества нулевого расширения на самом деле потребуются больше, поэтому это экономит лишнюю строку кодаmov rax, 0
. Гарантируя она всегда будет равна нулю продлен до 64 бит, компиляторы могут работать с этой аксиомой в виду , в то время как вmov rdx, rax
,rax
только должен ждать своей единственной зависимости, то есть он может начать выполнение быстрее и удалиться, освобождая исполнительных блоков. Кроме того, он также позволяет использовать более эффективные нулевые идиомы, такие какxor eax, eax
ноль,rax
без использования байта REX.источник
cmovbe
же 2 мопса, ноcmovb
1). Но ни один процессор, который выполняет частичное переименование регистров, не делает это так, как вы предлагаете. Вместо этого они вставляют UOP слияния, если частичный регистр переименован отдельно от полного (т.е. "грязный"). См. Почему GCC не использует частичные регистры? и как именно работают частичные регистры на Haswell / Skylake? Написание AL, похоже, ложно зависит от RAX, а AH непоследовательноThis gives a delay of 5 - 6 clocks. The reason is that a temporary register has been assigned to AL to make it independent of AH. The execution unit has to wait until the write to AL has retired before it is possible to combine the value from AL with the value of the rest of EAX
Я не могу найти пример «слияния uop», который можно было бы использовать для решения этой проблемы, то же самое для частичного сваливания флагаmov al, [mem]
что это микропредохранительная нагрузка + ALU- merge, переименовывая только AH, а UOP-объединение AH все еще выдает один. Механизмы слияния частичных флагов в этих процессорах различаются, например, Core2 / Nehalem по-прежнему просто останавливается для частичных флагов, в отличие от частичной регистрации.