Теперь у меня есть два противоречивых утверждения. Утверждение 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
Компиляторам нравится, когда все регистры могут использоваться для чего угодно, только ограничивая распределение регистров для нескольких особых случаев. Это то, что означает регистр ортогональности.