Почему rbp и rsp называются реестрами общего назначения?

Согласно Intel в x64, следующие регистры называются регистрами общего назначения (RAX, RBX, RCX, RDX, RBP, RSI, RDI, RSP и R8-R15) https://software.intel.com/en-us/articles/introduction-to-x64-assembly.

В следующей статье написано, что RBP и RSP являются специальными регистрами (RBP указывает на базу текущего кадра стека, а точка RSP - на верхнюю часть текущего кадра стека). https://www.recurse.com/blog/7-understanding-c-by-learning-assembly

Теперь у меня есть два противоречивых утверждения. Утверждение Intel должно быть доверенным, но что правильно и почему RBP и RSP вообще называются вообще?

Спасибо за любую помощь.

Ответ 1

Общее назначение означает, что все эти регистры могут использоваться с любыми инструкциями, выполняющими вычисления с регистрами общего назначения, в то время как, например, вы не можете делать все, что хотите, с помощью указателя инструкции (RIP) или регистра флагов (RFLAGS).

Некоторые из этих регистров были предусмотрены для использования для конкретного использования и обычно являются. Наиболее важными из них являются RSP и RBP.

Если вам нужно использовать их для своей собственной цели, вы должны сохранить их содержимое, прежде чем хранить что-то еще внутри, и при необходимости восстановить их исходное значение.

Ответ 2

Если регистр может быть операндом для add или использоваться в режиме адресации, то он "общего назначения", в отличие от регистров, таких как регистр сегмента FS или RIP. Регистры GP также называются "целочисленными регистрами", хотя регистры других типов также могут содержать целые числа.

В компьютерной архитектуре процессоры обычно обрабатывают целочисленные регистры/инструкции отдельно от регистров/инструкций FP/SIMD. Например, процессоры семейства Intel Sandybridge имеют отдельные физические файлы регистров для переименования целочисленных GP или векторных регистров FP. Они просто называются целочисленными или регистровыми файлами FP. (Где FP - это сокращение от всего, что ядру не нужно сохранять/восстанавливать, чтобы использовать регистры GP, оставляя нетронутым состояние FPU/SIMD в пользовательском пространстве.) Каждая запись в файле регистров FP имеет ширину 256 бит (для содержит вектор AVX ymm), но записи в целочисленном регистре должны иметь ширину только 64 бита.

На процессорах, которые переименовывают регистры сегментов (Skylake нет), я думаю, это будет частью целочисленного состояния, как и RFLAGS + RIP. Но когда мы говорим "регистр целых чисел", мы обычно имеем в виду именно регистр общего назначения.


Каждый регистр имеет некоторые особенности для некоторых инструкций, за исключением некоторых совершенно новых регистров, добавленных с x86-64: R8-R15. Они не дисквалифицируют их как общие цели (низкие 16 из) оригинальных 8 датируются 8086 годом, и было неявное использование каждого из них даже в оригинальном 8086.

Для RSP это специально для push/pop/call/ret, поэтому большая часть кода никогда не использует его ни для чего другого. (А в режиме ядра используется асинхронно для прерываний, так что вы действительно не можете спрятать его где-нибудь, чтобы получить дополнительный регистр GP, как вы можете в коде пользовательского пространства: является ли ESP универсальным, как EAX?)

Но в управляемых условных выражениях (например, без обработчиков сигналов) вам не нужно использовать RSP для указателя стека. Например, вы можете использовать его для чтения массива в цикле с помощью pop, как в этом коде-ответе. (На самом деле я использовал esp в 32-битном коде, но разница та же: pop работает быстрее, чем lodsd на Skylake, в то время как оба имеют lodsd 1 байт.)


Неявное использование и особенность для каждого регистра:

См. Также Сборка x86. Почему [e] bx сохраняется в соглашениях о вызовах? для частичного списка.

Я в основном ограничиваю это инструкциями из пользовательского пространства, особенно теми, которые современный компилятор может фактически генерировать из кода C или C++. Я не пытаюсь быть исчерпывающим для регистров, которые имеют много скрытого использования.

  • rax: однооперандный [i] mul/[i] div/cdq/cdqe, строковые инструкции (stos), cmpxchg и т.д. и т.д.), а также специальные более короткие кодировки для многих непосредственных инструкций, таких как 2-байтовый cmp al, 1 или 5-байтовое add eax, 12345 (без байта ModRM). Смотрите также codegolf.SE Советы по игре в гольф в машинном коде x86/x64.

    Также существует xchg -eax, откуда и 0x90 nop (прежде чем nop стал отдельно документированной инструкцией в x86-64, потому что xchg eax,eax нулевым xchg eax,eax расширяет eax на RAX и, следовательно, не может использовать кодировку 0x90. Но xchg rax,rax все еще может собираться в REX.W = 1 0x90.)

  • rcx: подсчет сдвига, подсчет rep -string, инструкция медленного loop
  • rdx: rdx:rax используется делением и умножением, и cwd/cdq/cqo для их настройки. rdtsc BMI2 mulx.
  • rbx: 8086 xlatb. cpuid использует все четыре из EAX..EDX. 486 cmpxchg8b, x86-64 cmpxchg16b. Большинство 32-битных компиляторов будут cmpxchg8 для std::atomic<long long>::compare_exchange_weak. (Чистая загрузка/чистое хранилище может использовать SSE MOVQ или x87 fild/fistp, хотя, если он предназначен для Pentium или более поздней версии.) 64-битные компиляторы будут использовать 64-битную lock cmpxchg, а не cmpxchg8b.

    Некоторые 64-битные компиляторы будут cmpxchg16b для atomic<struct_16_bytes>. RBX имеет наименьшее количество неявных применений оригинальной версии 8, но lock cmpxchg16b - один из немногих компиляторов, которые будут фактически использоваться.

  • rsi/rdi: строковые rep movsb, включая rep movsb которые иногда rep movsb некоторые компиляторы. (в некоторых случаях gcc также указывает rep cmpsb для строковых литералов, но это, вероятно, не оптимально).
  • rbp: leave (только на 1 моп медленнее, чем mov rsp, rbp/pop rbp. gcc фактически использует его в функциях с указателем фрейма, когда он не может просто pop rbp). Также ужасно медленный enter который никто никогда не использует.
  • rsp: стек операций: push/pop/call/ret и leave. (И enter). А в режиме ядра (не в пользовательском пространстве) используется асинхронное оборудование для сохранения контекста прерывания. Вот почему код ядра не может иметь красную зону.

  • r11: syscall/sysret использует его для сохранения/восстановления пространства пользователя RFLAGS. (Наряду с RCX для сохранения/восстановления пользовательского пространства RIP).

Особые случаи кодирования в режиме адресации:

(См. Также rbp, не разрешенный в качестве базы SIB? Который как раз касается режимов адресации, куда я скопировал эту часть этого ответа.)

rbp/r13 не может быть базовым регистром, без смещения: что кодирование вместо означает: (в ModRM) rel32 (РИПЫ-родственник), или (в SIB) disp32 без какого - либо базового регистра. (r13 использует те же 3 бита в ModRM/SIB, поэтому этот выбор упрощает декодирование, не заставляя декодер длины команды смотреть на бит REX.B, чтобы получить 4-й бит базового регистра). [r13] собирается в [r13 + disp8=0]. [r13+rdx] собирается в [rdx+r13] (чтобы избежать проблемы путем замены базы/индекса, когда это возможно).

rsp/r12 как базовый регистр всегда нуждается в байте SIB. (Кодирование ModR/M base = RSP является escape-кодом для сигнализации байта SIB, и, опять же, больше декодера должно заботиться о префиксе REX, если r12 обрабатывается по-другому).

rsp не может быть индексным регистром. Это позволяет кодировать [rsp], что более полезно, чем [rsp + rsp]. (Корпорация Intel могла бы разработать кодировки ModRM/SIB для 32-битных режимов адресации (впервые в 386), поэтому SIB без индекса возможен только при base = ESP. Это сделает [eax + esp*4] возможным и только исключить [esp + esp*1/2/4/8]. Но это бесполезно, поэтому они упростили аппаратное обеспечение, сделав index = ESP кодом без индекса независимо от базы. Это позволяет использовать два избыточных способа кодирования любой базы или режим адресации base + disp: с или без SIB.)

r12 может быть индексным регистром. В отличие от других случаев, это не влияет на декодирование длины команды. Кроме того, его нельзя обойти с помощью более длинной кодировки, как в других случаях. AMD хотела, чтобы регистр AMD64 был как можно более ортогональным, поэтому имеет смысл потратить несколько дополнительных транзисторов на проверку REX.X как часть декодирования индекса/без индекса. Например, [rsp + r12*4] требует index = r12, так что если r12 не является полностью предназначенным, то AMD64 станет худшей целью компилятора.

   0:   41 8b 03                mov    eax,DWORD PTR [r11]
   3:   41 8b 04 24             mov    eax,DWORD PTR [r12]      # needs a SIB like RSP
   7:   41 8b 45 00             mov    eax,DWORD PTR [r13+0x0]  # needs a disp8 like RBP
   b:   41 8b 06                mov    eax,DWORD PTR [r14]
   e:   41 8b 07                mov    eax,DWORD PTR [r15]
  11:   43 8b 04 e3             mov    eax,DWORD PTR [r11+r12*8] # *can* be an index

Компиляторам нравится, когда все регистры могут использоваться для чего угодно, только ограничивая распределение регистров для нескольких особых случаев. Это то, что означает регистр ортогональности.