Существует три вида ходов для заполнения 64-битного регистра:
Переход к младшей 32-битной части : B8 +rd id
, 5 байтов
Пример: mov eax, 241
/ mov[l] $241, %eax
Переход к нижней 32-битной части обнулит верхнюю часть.
Переход с 64-битнойнемедленный : 48 B8 +rd io
, 10 байтов
Пример: mov rax, 0xf1f1f1f1f1f1f1f1
/ mov[abs][q] $0xf1f1f1f1f1f1f1f1, %rax
Перемещение полного 64-битного немедленного.
Перемещение с 32-разрядным немедленным расширением знака : 48 C7 /0 id
, 7 байтов
Пример: mov rax, 0xffffffffffffffff
/ mov[q] $0xffffffffffffffff, %rax
Перемещение со знаком32-битный регистр с немедленным заполнением до 64-битного.
Обратите внимание, что на уровне сборки есть место для неопределенности , movq
используется для второго и третьего случая.
Для каждого непосредственного значения мы имеем:
- (a) Значения в [0, 0x7fff_ffff] могут быть закодированы с помощью (1), (2) и(3).
- (b) Значения в [0x8000_0000, 0xffff_ffff] можно кодировать с помощью (1) и (2).
- (c) Значения в [0x1_0000_0000, 0xffff_ffff_7fff_ffff] может быть закодировано с помощью (2)
- (d) значений в [0xffff_ffff_8000_0000, 0xffff_ffff_ffff_ffff] ) и может быть закодировано 3 1058 *) и может быть закодировано 3 1058 *) (3) и может быть закодировано 3 1058 *) и может быть закодировано 3 1058 *) и может быть закодировано 3 1058 *) (3) и (можно) кодировать с помощью 3 1058 *) (3).
Во всех случаях, кроме третьего, есть как минимум две возможные кодировки.
Ассемблер обычно выбирает самую короткую, если доступно более одной кодировки, но это не всегда так.
Для ГАЗА:
movabs[q]
всегда соответствует (2).
mov[q]
соответствует (3) для случаев (a) и (d), (2) длядругие случаи.
Он никогда не генерирует (1) для перехода в 64-битный регистр.
Чтобы его забрать (1), мы должны использовать mov[l] $0xffffffff, %edi
, что эквивалентно (я думаю, что GAS не будет преобразовывать переход в 64-битный регистр в один в его более низкий 32-битный регистр, дажекогда это эквивалентно).
В 16/32-битную эру, различие между (1) и (3) не считалось действительно важным (все же в GAS можно выбрать один конкретныйform ), поскольку это была не операция расширения знака, а артефакт оригинальной кодировки в 8086.
Инструкция mov
никогда не разделялась на две формы для учета (1) и (3) вместо этого один mov
использовался с ассемблером, почти всегда выбирающим (1) вместо (3).
С новыми 64-битными регистрами, имеющими 64-битные непосредственные значения, код тоже был бы слишком далекоразреженный (и может легко нарушить текущую максимальную длину команды в 16 байтов), поэтому не стоит расширять (1), чтобы всегда принимать 64-битное непосредственное значение.
Вместо этого (1) все еще имеют 32-битное непосредственное значение и ноль-расширяется (чтобы сломать любые ложные данныеЗависимость) и (2) были введены для редкого случая, когда на самом деле нужен 64-битный непосредственный операнд.
Если воспользоваться возможностью, (3) также был изменен на все еще с 32-битным немедленнымно также подписать и расширить его.
(1) и (3) должно быть достаточно для наиболее распространенных непосредственных (например, 1 или -1).
Однако разница между (1) / (3) и (2) глубже, чем разница в прошлом между (1) и (3), поскольку в то время как (1) и (3) оба имеют операндтот же размер, 32-разрядный, (3) имеет 64-разрядный непосредственный операнд.
Зачем нужна искусственно удлиненная инструкция?
Одним из вариантов использования может быть заполнение, так что следующий цикл будет кратен 16/32 байтов.
Этопожертвуйте ресурсы во внешнем интерфейсе (больше места в кеше команд) для ресурсов во внутреннем интерфейсе (меньше uOP, чем при заполнении без операционных инструкций).
Другой, более частый, случай использования - это когда нужно всего лишь создать шаблон машинного кода.
Например, в JIT может потребоваться подготовить последовательность инструкций для использования и заполнить значения немедленных значенийтолько во время выполнения.
В этом случае использование (2) значительно упростит обработку, поскольку всегда есть место для всех возможных значений.
Другой случай касается некоторой функциональности исправления, в отладочной версии программного обеспечения определенные вызовы могут быть сделаны косвенно с адресом в регистре, который только что был загружен с (2), так что отладчик может легко перехватить вызов на любойновая цель.