X86_64 регистрирует rax/eax/ax/al, перезаписывая полное содержимое регистра

Как широко рекламируется, современные процессоры x86_64 имеют 64-разрядные регистры, которые могут использоваться в обратном режиме, как 32-разрядные регистры, 16-разрядные регистры и даже 8-битные регистры, например:

0x1122334455667788
  ================ rax (64 bits)
          ======== eax (32 bits)
              ====  ax (16 bits)
              ==    ah (8 bits)
                ==  al (8 bits)

Такая схема может быть взята буквально, то есть всегда можно получить доступ только к части регистра, используя указанное имя для чтения или записи, и это было бы очень логично. Фактически, это верно для всех до 32 бит:

mov  eax, 0x11112222 ; eax = 0x11112222
mov  ax, 0x3333      ; eax = 0x11113333 (works, only low 16 bits changed)
mov  al, 0x44        ; eax = 0x11113344 (works, only low 8 bits changed)
mov  ah, 0x55        ; eax = 0x11115544 (works, only high 8 bits changed)
xor  ah, ah          ; eax = 0x11110044 (works, only high 8 bits cleared)
mov  eax, 0x11112222 ; eax = 0x11112222
xor  al, al          ; eax = 0x11112200 (works, only low 8 bits cleared)
mov  eax, 0x11112222 ; eax = 0x11112222
xor  ax, ax          ; eax = 0x11110000 (works, only low 16 bits cleared)

Однако, как только мы добираемся до 64-битных вещей, ситуация выглядит довольно неудобной:

mov  rax, 0x1111222233334444 ;           rax = 0x1111222233334444
mov  eax, 0x55556666         ; actual:   rax = 0x0000000055556666
                             ; expected: rax = 0x1111222255556666
                             ; upper 32 bits seem to be lost!
mov  rax, 0x1111222233334444 ;           rax = 0x1111222233334444
mov  ax, 0x7777              ;           rax = 0x1111222233337777 (works!)
mov  rax, 0x1111222233334444 ;           rax = 0x1111222233334444
xor  eax, eax                ; actual:   rax = 0x0000000000000000
                             ; expected: rax = 0x1111222200000000
                             ; again, it wiped whole register

Такое поведение кажется мне очень смешным и нелогичным. Похоже, что вообще пытаться написать что-либо на eax, приводит к вытиранию высоких 32 бит регистра rax.

Итак, у меня есть 2 вопроса:

  • Я считаю, что это неудобное поведение должно быть документировано где-то, но я не могу найти подробного объяснения (о том, как точно 32 бита 64-битного регистра будут стерты) в любом месте. Правильно ли, что письмо в eax всегда стирает rax, или это что-то более сложное? Это относится ко всем 64-битным регистрам, или есть некоторые исключения?

    A связанный с этим вопрос упоминает одно и то же поведение, но, увы, снова нет точных ссылок на документацию.

    Другими словами, мне нужна ссылка на документацию, которая определяет это поведение.

  • Является ли это всего лишь мной, или все это кажется действительно странным и нелогичным (т.е. eax-ax-ah-al, rax-ax-ah-al, имеющее одно поведение, а rax-eax - другое)? Может быть, мне не хватает какой-то жизненно важной точки здесь, почему это было реализовано так?

    Объяснение "почему" было бы высоко оценено.

Ответ 1

Модель процессора, описанная в руководстве по процессору Intel/AMD, является довольно несовершенной моделью для реального механизма разработки современного ядра. В частности, понятие регистров процессора не соответствует действительности, нет такой вещи, как регистр EAX или RAX.

Одним из основных заданий декодера команд является преобразование устаревших инструкций x86/x64 в микрооперации, инструкции RISC-подобного процессора. Небольшие инструкции, которые легко выполнять одновременно и могут использовать преимущества нескольких подблоков выполнения. Разрешить одновременное выполнение всего 6 команд.

Для выполнения этой работы также виртуализируется понятие регистров процессора. Декодер команд выделяет регистр из большого банка регистров. Когда инструкция удаляется, значение этого динамически распределенного регистра записывается обратно в любой регистр, в котором в настоящее время хранится значение, скажем, RAX.

Чтобы сделать эту работу плавно и эффективно, позволяя выполнять несколько команд одновременно, очень важно, чтобы эти операции не имели взаимозависимости. И худший вид, который вы можете иметь, заключается в том, что значение регистра зависит от других инструкций. Регистр EFLAGS известен, многие инструкции изменяют его.

Такая же проблема с тем, как вам нравится работать. Большая проблема, она требует, чтобы два значения регистра были объединены, когда инструкция удалена. Создание зависимости данных, которая будет забивать ядро. Заставляя верхние 32-битные значения 0, эта зависимость мгновенно исчезает, больше не требуется слияние. Скорость выполнения Warp 9.