Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье описываются стандартные процессы и соглашения, которые использует одна функция (вызывающий объект) для выполнения вызовов в другую функцию (вызываемую функцию) в коде x64.
Дополнительные сведения о соглашении вызовов __vectorcall см. в __vectorcall.
Дополнительные сведения о соглашении вызовов __preserve_none см. в __preserve_none.
Соглашения о вызовах по умолчанию
В двоичном интерфейсе приложений x64 (ABI) по умолчанию используется соглашение о вызове fast-call с четырьмя регистрами. Место в стеке вызовов выделяется как теневой стек для вызываемых процедур с целью сохранения регистров.
Между аргументами вызова функции и регистрами, используемыми для этих аргументов, существует однозначное соответствие. Любой аргумент, который не умещается в 8 байтов или не равен 1, 2, 4 или 8 байтам, должен передаваться по ссылке. Один аргумент никогда не распределяется между несколькими регистрами.
Стек регистров x87 не используется. Он может использоваться вызываемой функцией, но учитывайте, что оно может быть непостоянным между вызовами функций. Все операции с числами с плавающей запятой выполняются с помощью 16 регистров XMM.
Целые аргументы передаются в регистрах RCX, RDXи R8R9. Аргументы с плавающей запятой передаются в XMM0L, XMM1LXMM2Lи XMM3L. 16-байтовые аргументы передаются по ссылке. Подробнее о передаче параметров см. в разделе Передача параметров. Эти регистры, а также RAX, R10, R11, XMM4 и XMM5, считаются волатильными, то есть могут быть изменены вызываемой функцией при возврате. Использование регистров подробно описано в использовании регистров x64 и регистраторах, сохраняемых вызывающим/вызываемым.
Для функций с прототипом все аргументы преобразуются в ожидаемые типы вызываемых объектов перед передачей. Вызывающая функция отвечает за выделение пространства для параметров вызываемой функции. Он всегда должен выделять достаточно места для хранения четырех параметров регистров, даже если вызываемый объект не принимает такое количество параметров. Это соглашение упрощает поддержку функций языка C без прототипов и функций vararg C/C++. Для функций vararg и функций без прототипа все значения с плавающей запятой должны дублироваться в соответствующем регистре общего назначения. Все параметры за пределами первых четырех регистров должны храниться в стеке после теневого хранилища перед вызовом. Подробные сведения о функции vararg см. в статье Функции vararg. Подробнее о функции без прототипа см. в разделе Функции без прототипов.
Выравнивание
Большинство структур выровнены в соответствии с естественным расположением. Основные исключения — это указатель стека и память malloc или alloca, которые выравниваются до 16 байт, чтобы обеспечить производительность. Выравнивание свыше 16 байт должно выполняться вручную. Поскольку 16 байт — это общий размер выравнивания для операций XMM, это значение должно подходить для большинства кодов. Дополнительные сведения о макете структуры и выравнивании см. в разделе "Тип x64" и макет хранилища. Подробнее о макете стека см. в разделе Использование стека x64.
Разматываемость
Листовые функции — это функции, которые не изменяют никакие невосстанавливаемые регистры. Нелистовая функция может изменить энергонезависимый объект RSP, например, вызвав другую функцию. Или это может изменить RSP, выделив больше места в стеке для локальных переменных. Чтобы восстановить регистры, не являющиеся пустотыми при обработке исключения, неклафинированные функции аннотируются статическими данными. Эти данные описывают, как правильно очистить функцию в произвольной инструкции. Эти данные хранятся в виде pdata или данных процедуры, которые, в свою очередь, ссылаются на xdata, данные обработки исключений. xdata содержит сведения о распаковке и может указывать на дополнительные pdata, a также функцию обработчика исключений.
Прологи и эпилоги имеют высокий уровень ограничений, чтобы их можно было правильно описать в xdata. Указатель стека должен оставаться выровненным до 16 байт в любом регионе кода, который не является частью эпилога или пролога, за исключением конечных функций. Конечные функции можно развернуть просто путем имитации возврата, поэтому pdata и xdata не требуются. Дополнительные сведения о правильной структуре прологов и эпилогов функции см. в разделе Пролог и эпилог в коде x64. Дополнительные сведения о механизме обработки и раскрутке исключений для pdata и xdata см. в разделе Обработка исключений в коде x64.
Передача параметров
По умолчанию соглашение о вызовах x64 передает первые четыре аргумента функции в регистрах. Регистры, используемые для этих аргументов, зависят от расположения и типа аргумента. Остальные аргументы передаются в стеке справа налево. Вызывающий резервирует необходимое пространство стека и записывает эти аргументы в память стека, используя инструкции записи или перемещения, при этом сохраняя 8-байтное выравнивание для каждого аргумента.
Аргументы целочисленного типа в первых четырех позициях слева передаются слева направо в RCX, RDX, R8 и R9 соответственно. Пятый и следующие аргументы передаются в стек, как описано выше. Все целочисленные аргументы в регистрах выравниваются по правому краю, поэтому вызываемый объект может игнорировать верхние биты регистра и получить доступ только к той части регистра, которая необходима.
Все аргументы с плавающей запятой одинарной и двойной точности среди первых четырех параметров передаются в XMM0 - XMM3 в зависимости от их позиции. Значения с плавающей запятой размещаются в целочисленных регистрах RCX, RDX, R8 и R9 только при наличии аргументов varargs. Дополнительные сведения см. в разделе Varargs. Аналогичным образом регистры игнорируются, XMM0 - XMM3 если соответствующий аргумент является целым числом или типом указателя.
Типы __m128, массивы и строки никогда не передаются с непосредственным значением. Вместо этого указатель передается в память, выделенную вызывающим объектом. Структуры и объединения размера 8, 16, 32 или 64 бита и __m64 передаются, как если бы они были целыми числами того же размера. Структуры или объединения других размеров передаются в качестве указателя в память, выделенную вызывающим объектом. Для этих агрегатных типов, которые передаются в виде указателя, в том числе __m128, выделенная вызывающей стороной временная память должна составлять 16 байт.
Встроенные функции, которые не выделяют пространство стека и не вызывают другие функции, иногда используют другие изменяемые регистры для передачи дополнительных аргументов регистра. Такая оптимизация возможна благодаря тесной привязке между компилятором и реализацией внутренней функции.
Вызываемая сторона отвечает за сброс параметров регистров в теневое пространство, если необходимо.
В следующей таблице приведены сводные сведения о передаче параметров, по их типу и порядку слева.
| Тип параметра | пятый и выше | четвертая | третий | секунда | крайний левый |
|---|---|---|---|---|---|
| с плавающей запятой | стек | XMM3 |
XMM2 |
XMM1 |
XMM0 |
| целое число | стек | R9 |
R8 |
RDX |
RCX |
Агрегаты (8, 16, 32 или 64 бит) и __m64 |
стек | R9 |
R8 |
RDX |
RCX |
| Другие агрегаты, например указатели | стек | R9 |
R8 |
RDX |
RCX |
__m128 как указатель |
стек | R9 |
R8 |
RDX |
RCX |
Пример передачи аргумента 1 — все целые числа
func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, f then e passed on stack
Пример передачи аргумента 2 — все числа с плавающей запятой
func2(float a, double b, float c, double d, float e, float f);
// a in XMM0, b in XMM1, c in XMM2, d in XMM3, f then e passed on stack
Пример передачи аргумента 3 — смешение целых чисел и чисел с плавающей запятой
func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e passed on stack
Пример передачи аргумента 4 — __m64, __m128 и агрегаты
func4(__m64 a, __m128 b, struct c, float d, __m128 e, __m128 f);
// a in RCX, ptr to b in RDX, ptr to c in R8, d in XMM3,
// ptr to f passed on stack, then ptr to e passed on stack
Функции с переменным количеством аргументов (Varargs)
Если параметры передаются через функции vararg (например, аргументы многоточия), применяется стандартное соглашение о передаче параметров регистров. Это соглашение включает в себя перенос пятого и последующих аргументов в стек. Ответственность вызываемой стороны — сбрасывать аргументы, которые были переданы по адресу. Для значений с плавающей запятой как регистр целых чисел, так и регистр чисел с плавающей запятой должны содержать значение, если вызываемый объект ожидает значение в целочисленных регистрах.
Функции без прототипа
Для функций, для которых нет полного прототипа, вызывающий объект передает целочисленные значения как целые числа, а числа с плавающей запятой — в виде чисел двойной точности. Для значений с плавающей запятой как регистр целых чисел, так и регистр чисел с плавающей запятой содержат значение с плавающей запятой, если вызываемый объект ожидает значение в целочисленных регистрах.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Возвращаемые значения
Скалярное возвращаемое значение, которое помещается в 64 бита, включая тип __m64, возвращается в RAX. Нескаларные типы, включая floats, doubles и векторные типы, такие как __m128, __m128i__m128d возвращаются в XMM0. Состояние неиспользуемых битов в значении, возвращенном RAX или XMM0 не определено.
Определенные пользователем типы можно вернуть из глобальных функций и статических функций-членов по значению. Чтобы вернуть определяемый пользователем тип по значению RAX, он должен иметь длину 1, 2, 4, 8, 16, 32 или 64 бита. В нем также должны отсутствовать заданные пользователем конструктор, деструктор или оператор назначения копирования. Он не может содержать частных или защищенных нестатических элементов данных и нестатических элементов данных ссылочного типа. В нем не должно быть базовых классов или виртуальных функций. Кроме того, он может содержать только элементы данных, которые также удовлетворяют этим требованиям. Это определение по сути совпадает с типом POD C++03. Поскольку определение изменилось в стандарте C++11, мы не рекомендуем использовать std::is_pod для этого. В противном случае вызывающий объект должен выделить память для возвращаемого значения и передать указатель на него в качестве первого аргумента. Оставшиеся аргументы затем перемещаются на один аргумент вправо. Тот же указатель должен быть возвращён вызываемой функцией в RAX.
В приведенных ниже примерах показан способ передачи параметров и возвращаемых значений для функций с указанными объявлениями.
Пример возвращаемого значения 1 — результат в 64 бита
__int64 func1(int a, float b, int c, int d, int e);
// Caller passes a in RCX, b in XMM1, c in R8, d in R9, e passed on stack,
// callee returns __int64 result in RAX.
Пример возвращаемого значения 2 — результат в 128 бит
__m128 func2(float a, double b, int c, __m64 d);
// Caller passes a in XMM0, b in XMM1, c in R8, d in R9,
// callee returns __m128 result in XMM0.
Пример возвращаемого значения 3 — результат типа пользователя по указателю
struct Struct1 {
int j, k, l; // Struct1 exceeds 64 bits.
};
Struct1 func3(int a, double b, int c, float d);
// Caller allocates memory for Struct1 returned and passes pointer in RCX,
// a in RDX, b in XMM2, c in R9, d passed on the stack;
// callee returns pointer to Struct1 result in RAX.
Пример возвращаемого значения 4 — результат пользовательского типа по значению
struct Struct2 {
int j, k; // Struct2 fits in 64 bits, and meets requirements for return by value.
};
Struct2 func4(int a, double b, int c, float d);
// Caller passes a in RCX, b in XMM1, c in R8, and d in XMM3;
// callee returns Struct2 result by value in RAX.
Регистрируемые вызывающей стороной и сохраняемые вызываемой стороной регистры
ABI x64 считает регистры RAX, RCX, RDX, R8, R9, R10, R11 и XMM0-XMM5 временными. Если они присутствуют, верхние части YMM0-YMM15 и ZMM0-ZMM15 также являются энергозависимыми. На AVX512VL , ZMMYMMи XMM регистры 16-31 также являются переменными. При наличии TMM поддержки AMX регистры плиток являются переменными. Регистр с произвольным доступом следует считать уничтоженными при вызове функций, если анализ, например оптимизация всей программы, не доказывает обратное.
В ABI x64 регистры RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, и XMM6-XMM15 считаются энергонезависимыми. Они должны быть сохранены и восстановлены с помощью функции, которая их использует.
При наличии поддержки APX регистры R16-R29 являются переменными.
R30 и R31 энергонезависимы.
Указатели функций
Указатели функций — это просто указатели на метку соответствующей функции. Для указателей функций не предусмотрено требование к содержанию (TOC).
Поддержка чисел с плавающей запятой для устаревшего кода
Регистры стека с плавающей запятой и MMX сохраняютсяMM0-MM7/ST0-ST7 в контекстных коммутаторах. Для этих регистров не предусмотрено явное соглашение о вызовах. Использование этих регистров строго запрещено в коде режима ядра.
FPCSR
Состояние регистра также включает управляющее слово x87 FPU. Соглашение о вызовах определяет, что регистр является неизменяемым.
Регистр управляющего слова x87 FPU задается с использованием следующих стандартных значений в начале выполнения программы.
| Регистр[бит] | Параметр |
|---|---|
FPCSR\[0:6] |
Исключения маскируют все 1s (все исключения маскируются) |
FPCSR\[7] |
Зарезервировано — 0 |
FPCSR\[8:9] |
Управление точностью — 10B (двойная точность) |
FPCSR\[10:11] |
Управление округлением — 0 (округление до ближайшего числа) |
FPCSR\[12] |
Управление бесконечностью — 0 (не используется) |
Вызываемая функция, которая изменяет любое из полей в FPCSR, должна восстановить их, прежде чем вернуть управление вызывающей стороне. Кроме того, вызывающая сторона, изменившая любое из этих полей, должна восстановить их стандартные значения перед вызовом, если только вызываемый не ожидает измененных значений по договоренности.
Существует два исключения правил о неизменяемости флагов управления.
В функциях, где задокументированное назначение данной функции — изменять энергонезависимые флаги
FPCSR.Если доказуемо корректно, что нарушение этих правил приводит к тому, что программа выполняется так же, как и программа, в которой эти правила не нарушаются (например, с помощью анализа всей программы).
Несмотря на то что он считается неволатильным, не существует статического дескриптора разворачивания стека, который описывал бы, где он был сохранён и откуда его следует восстанавливать. Код, безопасный в отношении исключений и изменяющий FPCSR, должен использовать обработчик завершения при исключении (например, деструктор C++ или блок __finally), чтобы явно восстановить его при раскрутке стека.
MXCSR
Состояние регистра также включает MXCSR. Соглашение о вызовах делит этот регистр на изменяемую часть и неизменяемую часть. Переменная часть состоит из шести флагов состояния, в MXCSR\[0:5]то время как остальная часть регистра MXCSR\[6:15]считается неактивной.
Неизменяемая часть задается со следующими стандартными значениями в начале выполнения программы:
| Регистр[бит] | Параметр |
|---|---|
MXCSR\[6] |
Денормализованные числа равны нулю — 0 |
MXCSR\[7:12] |
Исключения маскируют все 1s (все исключения маскируются) |
MXCSR\[13:14] |
Управление округлением — 0 (округление до ближайшего числа) |
MXCSR\[15] |
Сброс до нуля для маскированного переполнения — 0 (отключено) |
Вызывающий объект, изменяющий любой из нелатильных полей в пределах MXCSR должен восстановить их перед возвратом вызывающему объекту. Кроме того, вызывающая сторона, изменившая любое из этих полей, должна восстановить их стандартные значения перед вызовом, если только вызываемый не ожидает измененных значений по договоренности.
Существует два исключения правил о неизменяемости флагов управления.
В функциях, где документированное назначение данной функции — изменять энергонезависимые флаги
MXCSR.Если доказуемо корректно, что нарушение этих правил приводит к тому, что программа выполняется так же, как и программа, в которой эти правила не нарушаются (например, с помощью анализа всей программы).
Не делайте предположений о состоянии изменяемой части регистра MXCSR при переходе через границу функции, если только документация функции явно не описывает это.
Несмотря на то, что некоторые части считаются нелатильными, не существует статического дескриптора очистки MXCSR , описывающего, откуда она была сохранена и откуда она должна быть восстановлена. Код, устойчивый к исключениям, который изменяет неизменяемые части MXCSR, должен использовать завершающую конструкцию обработки исключения (например, деструктор C++ или блок __finally), чтобы явно восстановить их при раскрутке стека.
setjmp/longjmp
При включении setjmpex.h или setjmp.h все вызовы setjmp или longjmp приводят к очистке, которая вызывает деструкторы и вызовы __finally. Это поведение отличается от x86, где включение setjmp.h приводит к тому, что __finally предложения и деструкторы не вызываются.
Вызов setjmp сохраняет текущий указатель стека, неизменяемые регистры и регистры MXCSR. Вызовы longjmp возвращают к месту последнего вызова setjmp и сбрасывают указатель стека, энергонезависимые регистры и регистры MXCSR к состоянию, сохраненному при последнем вызове setjmp.
Если APX поддерживается, R30 и R31 не следует изменять в рамках функции с момента вызова setjmp до момента выполнения вызова, который в конечном итоге приводит к выполнению longjmp. Это ограничение связано с тем, что R30 и R31 не сохраняются как часть jmp_buf — это определение структуры не может быть изменено. Вместо этого они восстанавливаются через распаку. В следующем примере показано, как разница в том, как данные восстанавливаются, влияет на это ограничение:
jmp_buf jmpbuffer;
void function_a() {
...
int val = setjmp(jmpbuffer); // At this time R30 is 10
...
if (val == 0) {
function_b(); // At this time R30 is 20
}
...
}
void function_b() {
...
longjmp(jmpbuffer, 1);
...
}
В этом примере значение R30 изменяется от точки, в которой вызывается setjmp, до точки, в которой вызывается function_b. В function_b, longjmp разворачивает стек, пока не дойдет до функции, которая вызвала setjmp (function_a в данном случае). Значение, восстановленное для R30, будет равно 20 (значению на момент вызова function_b), а не 10 (значению на момент вызова setjmp). Это означает, что когда setjmp возвращается во второй раз (в результате longjmp), значение R30 будет установлено в 20 вместо 10, что неверно. Именно поэтому компиляторы должны обеспечивать, чтобы R30 и R31 оставались неизменными с момента вызова setjmp до последнего места в функции, которое в конечном итоге может привести к вызову longjmp.
Так как longjmp можно вызывать из фильтра исключений, а не только из подпрограммы, это фактически означает, что R30 и R31 должны оставаться неизменными с того момента, как вызывается setjmp, и до конца функции.