У AMD есть спецификация ABI, в которой описывается соглашение о вызовах для использования на x86-64. Все операционные системы следуют ему, за исключением Windows, которая имеет собственное соглашение о вызовах x86-64. Зачем?
Кто-нибудь знает технические, исторические или политические причины этой разницы, или это чисто синдром NIH?
Я понимаю, что разные ОС могут иметь разные потребности в вещах более высокого уровня, но это не объясняет, почему, например, порядок передачи параметров реестра в Windows такой, rcx - rdx - r8 - r9 - rest on stack
как все остальные rdi - rsi - rdx - rcx - r8 - r9 - rest on stack
.
PS Я знаю , как эти соглашения о вызовах в целом отличаются , и я знаю , где найти информацию , если мне нужно. Я хочу знать почему .
Изменить: чтобы узнать как, см., Например, запись в Википедии и ссылки оттуда.
источник
Ответы:
Выбор четырех регистров аргументов на x64 - общий для UN * X / Win64
При использовании x86 следует помнить, что имя регистра в кодировке "reg number" неочевидно; с точки зрения кодирования инструкций ( байт MOD R / M , см. http://www.c-jump.com/CIS77/CPU/x86/X77_0060_mod_reg_r_m_byte.htm ), номера регистров 0 ... 7 - в таком порядке -
?AX
,?CX
,?DX
,?BX
,?SP
,?BP
,?SI
,?DI
.Следовательно, выбор A / C / D (regs 0..2) для возвращаемого значения и первых двух аргументов (что является «классическим» 32-битным
__fastcall
соглашением) является логическим выбором. Что касается перехода на 64-битную версию, то заказываются «более высокие» регистры, и Microsoft и UN * X / Linux выбралиR8
/R9
как первые.Имея это в виде, выбор Microsoft по
RAX
(возвращаемое значение) иRCX
,RDX
,R8
,R9
(Arg [0..3]) является понятным выбором , если вы выбираете четыре регистра для аргументов.Я не знаю, почему AMD64 UN * X ABI выбрал
RDX
раньшеRCX
.Выбор шести регистров аргументов на x64 - для UN * X
UN * X на архитектурах RISC традиционно выполняет передачу аргументов в регистрах - в частности, для первых шести аргументов (по крайней мере, для PPC, SPARC, MIPS). Это может быть одной из основных причин, по которой разработчики AMD64 (UN * X) ABI решили использовать шесть регистров и в этой архитектуре.
Так что если вы хотите шесть регистров для передачи аргументов в, и это логично выбрать
RCX
,RDX
,R8
иR9
четыре из них, которые других двух вы должны выбрать?«Старшие» регистры требуют дополнительного байта префикса инструкций для их выбора и, следовательно, имеют больший размер инструкции, поэтому вы не захотите выбирать какой-либо из них, если у вас есть опции. Из классических регистров, из - за неявный смысл
RBP
иRSP
они не доступны, иRBX
традиционно имеет особое применение на ООН * X (глобальная таблица смещений) , которые , казалось бы , дизайнеры AMD64 ABI не хотели понапрасну стать несовместимым с.Ergo, единственным выбором были
RSI
/RDI
.Итак, если вам нужно использовать
RSI
/ вRDI
качестве регистров аргументов, какими аргументами они должны быть?Изготовление их
arg[0]
иarg[1]
имеет ряд преимуществ. См. Комментарий cHao.?SI
и?DI
являются операндами источника / назначения строковых инструкций, и, как упоминалось в cHao, их использование в качестве регистров аргументов означает, что с соглашениями о вызовах AMD64 UN * X простейшая возможнаяstrcpy()
функция, например, состоит только из двух инструкций ЦП,repz movsb; ret
поскольку источник / цель адреса были помещены вызывающим абонентом в правильные регистры. Есть, в частности, в низкоуровневом и сгенерированном компилятором "связующем" коде (подумайте, например, о некоторых распределителях кучи C ++, заполняющих объекты нулями при создании, или страницы кучи ядра с нулевым заполнением наsbrk()
, или ошибки страницы копирования при записи) огромное количество блоков копирования / заполнения, поэтому это будет полезно для кода, который так часто используется для сохранения двух или трех инструкций ЦП, которые в противном случае загружали бы такие аргументы исходного / целевого адреса в "правильные" регистры.Таким образом , в некотором смысле, UN * X и Win64 отличаются только тем , что UN * X «помещает» два дополнительных аргумента, в целенаправленно выбранных
RSI
/RDI
регистров, к естественному выбору четырех аргументов вRCX
,RDX
,R8
иR9
.За гранью этого ...
Между UN * X и Windows x64 ABI есть больше различий, чем просто отображение аргументов в определенные регистры. Для обзора Win64 проверьте:
http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx
Win64 и AMD64 UN * X также разительно отличаются по способу использования пространства стека; в Win64, например, вызывающий должен выделить пространство стека для аргументов функции, даже если в регистрах передаются аргументы 0 ... 3. В UN * X, с другой стороны, листовая функция (то есть та, которая не вызывает другие функции) даже не требуется для выделения пространства стека вообще, если ей требуется не более 128 байтов (да, вы владеете и можете использовать определенное количество стека без выделения его ... ну, если вы не код ядра, источник изящных ошибок). Все это конкретные варианты оптимизации, большая часть обоснования которых объясняется в полных ссылках на ABI, на которые указывает ссылка в Википедии исходного постера.
источник
__fastcall
на 100% идентичны в случае наличия не более двух аргументов не более 32 бит и возврата значения не более 32 бит. Это немалый класс функций. Такая обратная совместимость между UN * X ABI для i386 / amd64 невозможна.memcpy
может быть реализовано таким образом, а неstrcpy
.IDK, почему Windows сделала то, что они сделали. Смотрите в конце этого ответа, чтобы предположить. Мне было любопытно, как было принято решение о вызовах SysV, поэтому я покопался в архиве списков рассылки и нашел кое-что интересное.
Интересно читать некоторые из этих старых веток в списке рассылки AMD64, поскольку архитекторы AMD активно участвовали в этом. например, выбор имен регистров был одной из сложных частей: AMD рассматривала переименование исходных 8 регистров r0-r7 или вызов новых регистров вроде
UAX
.Кроме того , обратная связь с ядром УБС идентифицированные вещи , которые сделали оригинальный дизайн
syscall
иswapgs
непригодным для использования . Вот как AMD обновила инструкцию, чтобы разобраться с этим перед выпуском каких-либо реальных чипов. Также интересно, что в конце 2000 года предполагалось, что Intel, вероятно, не станет использовать AMD64.Соглашение о вызовах SysV (Linux) и решение о том, сколько регистров следует сохранять для вызываемого абонента по сравнению с сохранением для вызывающего, было принято первоначально в ноябре 2000 года Яном Хубицкой (разработчиком gcc). Он скомпилировал SPEC2000 и посмотрел на размер кода и количество инструкций. В этой дискуссии обсуждаются некоторые из тех же идей, что и ответы и комментарии на этот вопрос SO. Во втором потоке он предложил текущую последовательность как оптимальную и, надеюсь, окончательную, генерируя меньший код, чем некоторые альтернативы .
Он использует термин «глобальный» для обозначения регистров с сохранением вызовов, которые должны быть выталкиваются / выталкиваются, если используются.
Выбор
rdi
,rsi
,rdx
как первые три арг был мотивирован:memset
или другую строковую функцию C в своих аргументах (где gcc встраивает операцию строки rep?)rbx
сохраняется вызов, потому что наличие двух регистров с сохранением вызовов, доступных без префиксов REX (rbx и rbp), является выигрышем. Предположительно выбран, потому что это единственный другой регистр, который неявно не используется ни одной инструкцией. (строка повторения, количество сдвигов и выходы / входы multi / div касаются всего остального).(фон:
syscall
/sysret
неизбежно уничтожаетrcx
(сrip
) иr11
(сRFLAGS
), поэтому ядро не может видеть, что было изначальноrcx
приsyscall
запуске.)ABI системного вызова ядра был выбран для соответствия вызову функции ABI, за исключением
r10
вместоrcx
, поэтому функции оболочки libc, такие какmmap(2)
can, простоmov %rcx, %r10
/mov $0x9, %eax
/syscall
.Обратите внимание, что соглашение о вызовах SysV, используемое i386 Linux, отстойно по сравнению с 32-битным __vectorcall в Windows. Он передает все в стеке и возвращается только
edx:eax
для int64, а не для небольших структур . Неудивительно, что для обеспечения совместимости с ним было приложено мало усилий. Когда нет причин не делать этого, они делали такие вещи, как сохранениеrbx
вызовов с сохранением, так как они решили, что иметь другой в исходной 8 (для которого не нужен префикс REX) было бы хорошо.Оптимизация ABI гораздо важнее в долгосрочной перспективе, чем любые другие соображения. Думаю, они проделали довольно хорошую работу. Я не совсем уверен в возвращении структур, упакованных в регистры, вместо разных полей в разных регистрах. Я думаю, что код, который передает их по значению, не работая с полями, таким образом побеждает, но дополнительная работа по распаковке кажется глупой. У них могло быть больше целочисленных регистров возврата, больше, чем просто
rdx:rax
, поэтому возвращение структуры с 4 членами могло вернуть их в rdi, rsi, rdx, rax или что-то в этом роде.Они рассматривали возможность передачи целых чисел в векторных регистрах, потому что SSE2 может работать с целыми числами. К счастью, они этого не сделали. Целые числа очень часто используются в качестве смещения указателей, а обращение к стековой памяти довольно дешево . Также инструкции SSE2 занимают больше байтов кода, чем целочисленные инструкции.
Я подозреваю, что разработчики Windows ABI, возможно, стремились минимизировать различия между 32 и 64 битами в интересах людей, которым приходится переносить asm с одного на другой или которые могут использовать пару
#ifdef
в некоторых ASM, чтобы один и тот же источник мог легче создавать 32- или 64-битная версия функции.Сведение к минимуму изменений в цепочке инструментов кажется маловероятным. Компилятору x86-64 нужна отдельная таблица, в которой указывается, какой регистр для чего используется и каково соглашение о вызовах. Небольшое перекрытие с 32-битной версией вряд ли приведет к значительной экономии в размере / сложности кода инструментальной цепочки.
источник
Помните, что Microsoft изначально «официально не возлагала никаких обязательств на первые попытки AMD64» (из «Истории современных 64-битных вычислений» Мэтью Кернера и Нила Пэджетта), потому что они были сильными партнерами Intel по архитектуре IA64. Я думаю, это означало, что даже если бы они в противном случае были бы открыты для работы с инженерами GCC над ABI для использования как в Unix, так и в Windows, они бы этого не сделали, поскольку это означало бы публичную поддержку усилий AMD64, когда они этого не сделали. Тем не менее официально это сделано (и, вероятно, расстроило бы Intel).
Вдобавок к этому, в те дни Microsoft не стремилась дружить с проектами с открытым исходным кодом. Уж точно не Linux или GCC.
Так зачем им сотрудничать по ABI? Я предполагаю, что ABI отличаются просто потому, что они были разработаны более или менее в одно и то же время и изолированно.
Еще одна цитата из «Истории современных 64-битных вычислений»:
Это указывает на то, что даже AMD не считала, что сотрудничество между MS и Unix обязательно является самым важным, но поддержка Unix / Linux была очень важной. Может быть, даже попытка убедить одну или обе стороны пойти на компромисс или сотрудничать не стоит усилий или риска (?) Разозлить кого-либо из них? Возможно, AMD думала, что даже предложение общего ABI может задержать или подорвать более важную цель - просто подготовить поддержку программного обеспечения, когда чип будет готов.
Предположения с моей стороны, но я думаю, что основная причина, по которой ABI отличаются, была политическая причина, по которой MS и стороны Unix / Linux просто не работали вместе над этим, и AMD не считала это проблемой.
источник
__vectorcall
потому что передача__m128
по стеку - отстой. Наличие семантики с сохранением вызовов для нижних 128b некоторых векторных регистров также является странным (отчасти вина Intel в том, что они не разработали расширяемый механизм сохранения / восстановления изначально с SSE, но все еще не с AVX.)alloca
или некоторых других случаев). Это нормально, если вы привыкли использоватьgcc -fomit-frame-pointer
Linux по умолчанию. ABI определяет метаданные размотки стека, которые позволяют обрабатывать исключения по-прежнему. (Я предполагаю, что это работает что-то вроде CFI GNU / Linux x86-64 System V.eh_frame
).gcc -fomit-frame-pointer
по умолчанию (с включенной оптимизацией) всегда на x86-64, и другие компиляторы (например, MSVC) делают то же самое.Win32 имеет собственное использование для ESI и EDI и требует, чтобы они не изменялись (или, по крайней мере, восстанавливались перед вызовом API). Я предполагаю, что 64-битный код делает то же самое с RSI и RDI, что объясняет, почему они не используются для передачи аргументов функций.
Однако я не могу сказать вам, почему переключаются RCX и RDX.
источник
__fastcall
соглашение о вызовах. Вы утверждаете Win32 / Win64 не совместим, но потом, посмотрите внимательно: Для функции , которая принимает два 32bit арга и возвращают 32 - битных, Win64 и Win32__fastcall
фактически являются 100% совместимыми (теми же регистрами для прохождения два 32bit арга, то же возвращаемое значения). Даже некоторый двоичный (!) Код может работать в обоих режимах работы. Сторона UNIX полностью отказалась от «старых путей». На то есть веские причины, но перерыв есть перерыв.