Почему Windows64 использует соглашение о вызовах, отличное от всех других ОС на x86-64? - PullRequest
92 голосов
/ 13 декабря 2010

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.

P.S. Я знаю, как эти соглашения о вызовах отличаются в целом, и я знаю, где найти детали, если мне нужно. То, что я хочу знать, это почему .

Редактировать: как, см., Например, запись в википедии и ссылки оттуда.

Ответы [ 4 ]

75 голосов
/ 14 декабря 2010

Выбор четырех регистров аргументов на 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 традиционно имеет специальное использование для UN * 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(), или копирование на -записать pagefaults) огромное количество блоков копирования / заполнения, следовательно, это будет полезно для кода, который так часто используется для сохранения двух или трех инструкций ЦП, которые в противном случае загружали бы такие аргументы адреса источника / цели в «правильные» регистры.

Таким образом, UN * X и Win64 отличаются только тем, что UN * X "добавляет" два дополнительных аргумента в специально выбранных регистрах RSI / RDI к естественному выбору четырех аргументов в RCX, RDX, R8 и R9.

Помимо этого ...

Существует больше различий между ABI UN * X и Windows x64, чем просто сопоставление аргументов с конкретными регистрами. Для обзора на Win64, проверьте:

http://msdn.microsoft.com/en-us/library/7kcdt6fy.aspx

Win64 и AMD64 UN * X также разительно отличаются тем, как используется стековое пространство; на Win64, например, вызывающий должен выделить стековое пространство для аргументов функции, даже если аргументы 0 ... 3 передаются в регистрах. В UN * X, с другой стороны, конечная функция (то есть та, которая не вызывает другие функции) даже не требуется для выделения стекового пространства вообще, если ей требуется не более 128 байт (да, вы владеете и можете использовать определенное количество стека без его выделения ... ну, если только вы не код ядра, источник изящных ошибок). Все это конкретные варианты оптимизации, большая часть которых объясняется в полных ссылках на ABI, на которые указывает ссылка на википедию оригинального плаката.

32 голосов
/ 25 февраля 2016

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

Интересно читать некоторые из этих старых тем на AMD64список рассылки, так как на нем активно работали архитекторы AMD.Например, выбор имен регистров был одной из трудных частей: AMD считала переименованием оригинальных 8 регистров r0-r7 или вызовом новых регистров, например UAX.

Также, отзывы от ядраРазработчики определили вещи, которые сделали оригинальный дизайн syscall и swapgs непригодным для использования .Вот как AMD обновила инструкцию , чтобы разобраться с этим перед выпуском любых реальных чипов.Также интересно, что в конце 2000 года предполагалось, что Intel, вероятно, не примет AMD64.


Соглашение о вызовах SysV (Linux) и решение о том, сколько регистров должно быть сохранено вызываемым абонентом по сравнению с. caller-save, был , сделанный первоначально в ноябре 2000 года Яном Хубицким (разработчик gcc).Он скомпилировал SPEC2000 и посмотрел размер кода и количество инструкций.Эта дискуссионная ветка связана с некоторыми из тех же идей, что и ответы и комментарии на этот SO вопрос.Во втором потоке он предложил текущую последовательность как оптимальную и, как мы надеемся, окончательную, генерируя меньший код, чем некоторые альтернативы .

Он использует термин «глобальный» для обозначения регистров с сохранением вызовов, чтодолжен быть нажат / вытолкнут при использовании.

Выбор rdi, rsi, rdx в качестве первых трех аргументов был мотивирован:

  • вспомогательный код-Сохранение размера в функциях, которые вызывают memset или другие строковые функции C в своих аргументах (где gcc вставляет строковую операцию rep?)
  • rbx сохраняется с сохранением вызова, поскольку имеет два сохраненных с вызовом регистра, доступных без REXпрефиксы (rbx и rbp) это выигрыш.Предположительно выбран, потому что это единственный другой тип, который неявным образом не используется ни одной инструкцией.(строка rep, счетчик сдвига и выходы / входы mul / div касаются всего остального).
  • Ни один из регистров специального назначения не сохраняется при сохранении вызова (см. точку приоритета), поэтому функция, которая хочет использовать repСтроковые инструкции или сдвиг счетчика переменных, возможно, должны перемещать аргументы функции куда-то еще, но не нужно сохранять / восстанавливать значение вызывающего.
  • Мы пытаемся избежать RCX в началепоследовательность, поскольку это регистр, который обычно используется для специальных целей, таких как EAX, поэтому он имеет ту же цель, что и отсутствует в последовательности.Также его нельзя использовать для системных вызовов, и мы бы хотели, чтобы последовательность системных вызовов максимально соответствовала последовательности вызовов функций.

    (background: syscall / sysret неизбежно уничтожает rcxrip) и r11RFLAGS), поэтому ядро ​​не может видеть, что было изначально в rcx, когда syscall работал.)

Системный вызов ядра ABI был выбран для соответствия вызову функции ABI, за исключением r10 вместо rcx, поэтому функции-обертки libc, такие как mmap(2), могут просто mov %rcx, %r10 / mov $0x9, %eax / syscall.


Обратите внимание, что соглашение о вызовах SysV, используемое i386 Linux, отстой по сравнению с 32-битным окном Windows __vectorcall. Он передает все в стек и возвращает только в edx:eax для int64, а не для небольших структур .Неудивительно, что было приложено мало усилий для обеспечения совместимости с ним.Когда нет причин не делать этого, они делали что-то вроде сохранения rbx сохраняемых вызовов, так как решили, что хорошо иметь другой в исходной 8 (которому не нужен префикс REX).

Создание оптимального ABI на намного более важно в долгосрочной перспективе, чем любое другое соображение.Я думаю, что они сделали довольно хорошую работу.Я не совсем уверен в возвращении структур, упакованных в регистры, вместо разных полей в разных регистрах.Я предполагаю, что код, который передает их по значению без фактической работы с полями, побеждает таким образом, но дополнительная работа по распаковке кажется глупой.У них могло бы быть больше целочисленных регистров возврата, чем просто rdx:rax, поэтому возвращение структуры с 4 членами могло бы вернуть их в rdi, rsi, rdx, rax или что-то в этом роде.

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


Я подозреваю, что разработчики Windows ABI, возможно, стремились минимизировать различия между 32 и 64 битами в интересах людей, которым приходится переносить asm с одного надругой, или который может использовать пару #ifdef s в некотором ASM, так что тот же источник может более легко построить 32- или 64-битную версию функции.

Минимизация изменений в цепочке инструментов кажется маловероятной.Компилятору x86-64 нужна отдельная таблица, для чего используется регистр, и каково соглашение о вызовах.Небольшое перекрытие с 32-битной версией вряд ли приведет к значительной экономии в размере / сложности кода набора инструментов.

12 голосов
/ 25 февраля 2016

Помните, что Microsoft изначально «официально не проявляла приверженности ранним усилиям AMD64» (из «Истории современных 64-битных вычислений» Мэтью Кернера и Нила Паджетта), потому что они были сильными партнерами Intel в архитектура IA64. Я думаю, это означало, что даже если бы они иначе были бы открыты для работы с инженерами GCC над ABI для использования как в Unix, так и в Windows, они бы этого не сделали, поскольку это означало бы публичную поддержку усилий AMD64, когда они не ' пока официально не сделал этого (и, вероятно, расстроил бы Intel).

Кроме того, в те времена у Microsoft не было абсолютно никаких оснований дружить с проектами с открытым исходным кодом. Конечно, не Linux или GCC.

Так почему они сотрудничали на ABI? Я предполагаю, что ABI отличаются просто потому, что они были разработаны более или менее в то же время и в изоляции.

Еще одна цитата из "Истории современных 64-битных вычислений":

Параллельно с сотрудничеством с Microsoft AMD также привлекла сообщество с открытым исходным кодом для подготовки к чипу. AMD заключила контракт с и Code Sorcery, и SuSE для цепочки инструментов (Red Hat уже была нанят Intel на порт цепочки инструментов IA64). Рассел объяснил, что SuSE создал C и FORTRAN компиляторы, а Code Sorcery - Паскаль компилятор. Вебер пояснил, что компания также занимается сообщество Linux, чтобы подготовить порт Linux. Это усилие было очень важно: это послужило стимулом для Microsoft продолжать инвестировать в усилия AMD64 для Windows, а также обеспечить Linux, который стала важной ОС в то время, будет доступна, как только чипы были выпущены.

Вебер заходит так далеко, что утверждает, что работа с Linux была абсолютно необходима. к успеху AMD64, потому что это позволило AMD производить сквозной Система без помощи других компаний, если это необходимо. это вероятность того, что у AMD была худшая стратегия выживания даже если другие партнеры отказались, что, в свою очередь, оставило других партнеров Занимался страхом остаться позади.

Это указывает на то, что даже AMD не чувствовала, что сотрудничество между MS и Unix было обязательно самым важным, но поддержка Unix / Linux была очень важна. Может быть, даже попытка убедить одну или обе стороны пойти на компромисс или сотрудничать не стоила усилий или риска (?) Раздражать кого-либо из них? Возможно, AMD подумала, что даже предложение общего ABI может отложить или сорвать более важную задачу - иметь готовую поддержку программного обеспечения, когда чип будет готов.

Предположение с моей стороны, но я думаю, что основной причиной, по которой ABI отличаются, была политическая причина, по которой стороны MS и Unix / Linux просто не работали над этим, и AMD не видела в этом проблемы.

12 голосов
/ 13 декабря 2010

Win32 имеет свои собственные применения для ESI и EDI и требует, чтобы они не были изменены (или, по крайней мере, чтобы они были восстановлены перед вызовом в API). Я думаю, что 64-битный код делает то же самое с RSI и RDI, что объясняет, почему они не используются для передачи аргументов функций.

Я не могу сказать вам, почему переключаются RCX и RDX.

...