Поделиться через


Соглашение о вызовах для 64-разрядных систем

В этом разделе описаны стандартные процессы и соглашения, которые одна функция (вызывающий объект) использует для выполнения вызовов к другой функции (вызываемый объект) в коде x64.

Дополнительные сведения о соглашении __vectorcall о вызовах см. в __vectorcall.

Соглашения о вызовах по умолчанию

Двоичный интерфейс для 64-разрядных приложений (ABI) по умолчанию использует соглашение о вызовах по четырем регистрам. Место в стеке вызовов выделяется как теневое хранилище для вызываемых функций сохранения этих регистров.

Между аргументами вызова функции и регистрами, используемыми для этих аргументов, существует однозначное соответствие. Любой аргумент, который не умещается в 8 байтов или не равен 1, 2, 4 или 8 байтам, должен передаваться по ссылке. Один аргумент никогда не распределяется между несколькими регистрами.

Стек регистров x87 не используется. Он может использоваться вызываемой функцией. Однако он должен рассматриваться как изменяемый в рамках вызовов функций. Все операции с числами с плавающей запятой выполняются с помощью 16 регистров XMM.

Целочисленные аргументы передаются в регистрах РККС, RDX, R8 и R9. Аргументы с плавающей запятой передаются в XMM0L, XMM1L, XMM2L и XMM3L. 16-байтовые аргументы передаются по ссылке. Подробнее о передаче параметров см. в разделе Передача параметров. Эти регистры и RAX, R10, R11, XMM4 и XMM5 считаются переменными или потенциально изменены вызывающим участником по возвращении. Регистрация использования подробно описана в регистре x64, а также сохраненные регистры вызывающего или вызывающего пользователя.

Для функций с прототипом все аргументы преобразуются в ожидаемые типы вызываемых объектов перед передачей. Вызывающий объект отвечает за выделение пространства для параметров вызываемого объекта. Он всегда должен выделять достаточно места для хранения четырех параметров регистров, даже если вызываемый объект не принимает такое количество параметров. Это соглашение упрощает поддержку функций языка C без прототипов и функций vararg C/C++. Для функций vararg и функций без прототипа все значения с плавающей запятой должны дублироваться в соответствующем регистре общего назначения. Все параметры за пределами первых четырех регистров должны храниться в стеке после теневого хранилища перед вызовом. Подробные сведения о функции vararg см. в статье Функции vararg. Подробнее о функции без прототипа см. в разделе Функции без прототипов.

Точное понимание

Большинство структур выровнены в соответствии с естественным выравниванием. Основные исключения — это указатель стека и память malloc или alloca, которые выравниваются до 16 байт, чтобы обеспечить производительность. Выравнивание свыше 16 байт должно выполняться вручную. Поскольку 16 байт — это общий размер выравнивания для операций XMM, это значение должно подходить для большинства кодов. Дополнительные сведения о макете структуры и выравнивании см. в разделе "Тип x64" и макет хранилища. Подробнее о макете стека см. в разделе Использование стека x64.

Раскрутка

Конечные функции — это функции, которые не изменяют неизменяемые регистры. Неконечная функция может изменить неизменяемый RSP, например путем вызова функции. или выделения дополнительного пространства стека для локальных переменных. Чтобы восстановить неизменяемые регистры при обработке исключения, неконечные функции должны быть снабжены статическими данными. Эти данные описывают, как правильно очистить функцию в произвольной инструкции. Эти данные хранятся в виде pdata или данных процедуры, которые, в свою очередь, ссылаются на xdata, данные обработки исключений. Значение xdata содержит сведения о раскрутке и может указывать на дополнительные pdata или функцию обработчика исключений.

Прологи и эпилоги имеют высокий уровень ограничений, чтобы их можно было правильно описать в xdata. Указатель стека должен оставаться выровненным до 16 байт в любом регионе кода, который не является частью эпилога или пролога, за исключением конечных функций. Конечные функции можно развернуть просто путем имитации возврата, поэтому pdata и xdata не требуются. Дополнительные сведения о правильной структуре прологов и эпилогов функции см. в разделе Пролог и эпилог в коде x64. Дополнительные сведения об обработке исключений, обработке исключений и раскрутке для pdata и xdata см. в разделе Обработка исключений в коде x64.

Передача параметров

По умолчанию соглашение о вызовах x64 передает первые четыре аргумента функции в регистрах. Регистры, используемые для этих аргументов, зависят от расположения и типа аргумента. Оставшиеся аргументы передаются в стек в порядке справа налево.

Целочисленные аргументы в первых четырех позициях передаются в порядке слева направо в RCX, RDX, R8 и R9, соответственно. Пятый и следующие аргументы передаются в стек, как описано выше. Все целочисленные аргументы в регистрах выравниваются по правому краю, поэтому вызываемый объект может игнорировать верхние биты регистра и получить доступ только к той части регистра, которая необходима.

Любые аргументы с плавающей запятой и двойной точностью в первых четырех параметрах передаются в XMM0–XMM3 (в зависимости от позиции). Значения с плавающей запятой помещаются в регистры целочисленных значений RCX, RDX, R8 и R9 только при наличии в них аргументов varargs. Дополнительные сведения см. в разделе Функции vararg. Аналогичным образом регистры XMM0–XMM3 игнорируются, если соответствующий аргумент является целым числом или указателем.

Типы __m128, массивы и строки никогда не передаются непосредственным значением. Вместо этого указатель передается в память, выделенную вызывающим объектом. Структуры и объединения размера 8, 16, 32 или 64 бит и __m64 передаются, как если бы они были целыми числами одного и того же размера. Структуры или объединения других размеров передаются в качестве указателя в память, выделенную вызывающим объектом. Для этих агрегатных типов, которые передаются в виде указателя, в том числе __m128, выделенная вызывающей стороной временная память должна составлять 16 байт.

Встроенные функции, которые не выделяют пространство стека и не вызывают другие функции, иногда используют другие изменяемые регистры для передачи дополнительных аргументов регистра. Такая оптимизация возможна благодаря тесной привязке между компилятором и реализацией внутренней функции.

Вызываемый объект отвечает за дамп параметров регистров в теневом пространстве, если потребуется.

В следующей таблице приведены сводные сведения о передаче параметров по типу и расположению слева.

Тип параметра пятый и выше четвертая третья вторая крайний левый
с плавающей запятой стек XMM3 XMM2 XMM1 XMM0
integer стек 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 pushed 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 pushed 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 pushed 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 pushed on stack, then ptr to e pushed on stack

Функции с переменным количеством аргументов (Varargs)

Если параметры передаются через функции vararg (например, аргументы многоточия), применяется стандартное соглашение о передаче параметров регистров. Это соглашение включает в себя перенос пятого и последующих аргументов в стек. Вызываемый объект отвечает за аргументы дампа, которые получают свой адрес. Для значений с плавающей запятой как регистр целых чисел, так и регистр чисел с плавающей запятой должны содержать значение, если вызываемый объект ожидает значение в целочисленных регистрах.

Функции без прототипа

Для функций, для которых нет полного прототипа, вызывающий объект передает целочисленные значения как целые числа, а числа с плавающей запятой — в виде чисел двойной точности. Для значений с плавающей запятой как регистр целых чисел, так и регистр чисел с плавающей запятой содержат значение с плавающей запятой, если вызываемый объект ожидает значение в целочисленных регистрах.

func1();
func2() {   // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
   func1(2, 1.0, 7);
}

Возвращаемые значения

Скалярное возвращаемое значение не больше 64 бит возвращается посредством RAX (сюда входит тип __m64). Нескалярные типы, включая типы с плавающей точкой, тип Double и векторные типы, такие как __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 pushed 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 pushed 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 регистры ZMM, YMM и XMM 16–31 также являются изменяемыми. При наличии поддержки AMX регистры плиток TMM являются переменными. Они должны считаться уничтоженными при вызовах функций (если иное не обеспечивается защитой при анализе, например при оптимизации всей программы).

В ABI x64 учитываются регистры RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15 и XMM6–XMM15 являются неизменяемыми. Они должны быть сохранены и восстановлены с помощью функции, которая их использует.

Указатели функций

Указатели функций — это просто указатели на метку соответствующей функции. Для указателей функций не предусмотрено требование к содержанию (TOC).

Поддержка чисел с плавающей запятой для устаревшего кода

Регистры MMX и стека с плавающей запятой (MM0-MM7/ST0-ST7) сохраняются во всех переключениях контекста. Для этих регистров не предусмотрено явное соглашение о вызовах. Использование этих регистров строго запрещено в коде режима ядра.

FPCSR

Состояние регистра также включает управляющее слово x87 FPU. Соглашение о вызовах определяет, что регистр является неизменяемым.

Регистр управляющего слова x87 FPU задается с использованием следующих стандартных значений в начале выполнения программы:

Register[bits] Параметр
FPCSR[0:6] Маски исключений все 1 (все исключения маскированы)
FPCSR[7] Зарезервировано — 0
FPCSR[8:9] Управление точностью — 10B (двойная точность)
FPCSR[10:11] Управление округлением — 0 (округление до ближайшего числа)
FPCSR[12] Управление бесконечностью — 0 (не используется)

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

Существует два исключения из правил, связанных с постоянством управляющих флагов:

  • В функциях, где документированная цель заданной функции заключается в изменении неизменяемых флагов FPCSR.

  • Если доказуемо корректно, что нарушение этих правил приводит к тому, что программа выполняется так же, как и программа, в которой эти правила не нарушаются (например, с помощью анализа всей программы).

MXCSR

Состояние регистра также включает MXCSR. Соглашение о вызовах делит этот регистр на изменяемую часть и неизменяемую часть. Переменная часть состоит из шести флагов состояния в MXCSR[0:5], в то время как остальная часть регистра MXCSR[6:15], считается неактивной.

Неизменяемая часть задается со следующими стандартными значениями в начале выполнения программы:

Register[bits] Параметр
MXCSR[6] Денормализованные числа равны нулю — 0
MXCSR[7:12] Маски исключений все 1 (все исключения маскированы)
MXCSR[13:14] Управление округлением — 0 (округление до ближайшего числа)
MXCSR[15] Сброс до нуля для маскирования неточного значения — 0 (отключено)

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

Существует два исключения из правил, связанных с постоянством управляющих флагов:

  • В функциях, где документированная цель заданной функции заключается в изменении неизменяемых флагов MXCSR.

  • Если доказуемо корректно, что нарушение этих правил приводит к тому, что программа выполняется так же, как и программа, в которой эти правила не нарушаются (например, с помощью анализа всей программы).

Никакие допущения не могут быть сделаны относительно состояния изменяемой части MXCSR в границах функции, кроме случаев, особо описанных в документации по функции.

setjmp/longjmp

При включении setjmpex.h или setjmp.h все вызовы к setjmp или longjmp приводят к очистке, вызывающей деструкторы и вызовы __finally. Это поведение отличается от поведения в архитектуре x86, где включение setjmp.h приводит к невозможности вызова предложений __finally и деструкторов.

Вызов setjmp сохраняет текущий указатель стека, неизменяемые регистры и регистры MXCSR. Вызовы longjmp возвращают к последнему месту вызова setjmp и сбрасывают указатель стека, непостоянные регистры и регистры MXCSR назад в состояние, сохраненное последним вызовом setjmp.

См. также

Программные соглашения для 64-разрядных систем