__vectorcall

Блок, относящийся только к системам Майкрософт

Соглашение __vectorcall о вызовах указывает, что аргументы функций передаются в регистрах, когда это возможно. __vectorcallиспользует больше регистров для аргументов, чем __fastcall используется соглашение о вызовах x64 по умолчанию. Соглашение о вызовах __vectorcall поддерживается только в машинном коде для процессоров x86 и x64, в которых используется набор инструкций SSE2 и выше. Используется для ускорения функций, которые передают несколько аргументов вектора с плавающей запятой или SIMD и выполняют операции, которые используют __vectorcall аргументы, загруженные в регистры. В следующем списке показаны функции, которые являются общими __vectorcallдля реализаций x86 и x64. Различия объясняются ниже в этом разделе.

Элемент Внедрение
Соглашение об оформлении имен C Имена функций суффиксируются двумя знаками at (@) и числом байтов (в десятичном) списке параметров.
Соглашение о преобразовании регистра Преобразование регистра не выполняется.

/Gv Использование параметра компилятора приводит к компиляции каждой функции в модуле, если __vectorcall функция не является функцией-членом, объявляется с конфликтующим атрибутом соглашения о вызове, использует vararg список аргументов переменной или имеет имяmain.

Можно передать три типа аргументов, регистрируя в __vectorcall функциях: целочисленные значения типа, значения векторного типа и однородные значения агрегатных векторов (HVA).

Целочисленный тип удовлетворяет двум требованиям: он соответствует собственному размеру регистра процессора ( например, 4 байта на компьютере x86 или 8 байтах на компьютере x64), и он преобразуется в целое число регистра и обратно, не изменяя его битовое представление. Например, любой тип, который может быть повышен до int x86 (long long в x64) (например, или charshortможет быть приведение int к (long long на x64) и обратно к исходному типу без изменений является целым типом. Целые типы включают указатель, ссылку и structunion типы 4 байта (8 байт в x64) или меньше. На платформах x64 большие struct и union типы передаются по ссылке на память, выделенную вызывающим объектом; на платформах x86 они передаются по значению в стеке.

Тип вектора — это тип с плавающей запятой( например, a float или doubleSIMD-векторный тип, __m128 например или __m256.

Тип HVA — это составной тип, в котором может содержаться до 4 элементов данных, имеющих идентичные векторные типы. Тип HVA предъявляет то же требование к выравниванию, что и векторный тип его членов. Это пример определения HVA struct , содержащего три идентичных типа векторов и имеющий 32-байтовое выравнивание:

typedef struct {
   __m256 x;
   __m256 y;
   __m256 z;
} hva3;    // 3 element HVA type on __m256

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

Функция-член может быть объявлена __vectorcall с помощью описателя. Скрытый this указатель передается регистром в качестве первого аргумента типа целочисленного типа.

На компьютерах __vectorcall ARM принимается и игнорируется компилятором. В ARM64EC не __vectorcall поддерживается и отклоняется компилятором.

Если используется внестрочное определение нестатической функции-члена класса, то модификатор соглашения о вызовах не должен быть задан во внестрочном определении. То есть для нестатических членов класса считается, что соглашение о вызовах, указанное во время объявления, было сделано в точке определения. Рассмотрим следующее определение класса:

struct MyClass {
   void __vectorcall mymethod();
};

вид:

void MyClass::mymethod() { return; }

эквивалентен следующему:

void __vectorcall MyClass::mymethod() { return; }

Модификатор __vectorcall соглашения о вызовах должен быть указан при создании указателя на __vectorcall функцию. В следующем примере создается typedef указатель __vectorcall на функцию, которая принимает четыре double аргумента и возвращает __m256 значение:

typedef __m256 (__vectorcall * vcfnptr)(double, double, double, double);

Для совместимости с предыдущими версиями является синонимом__vectorcall, _vectorcall если не указан параметр /Za компилятора (отключить расширения языка).

Соглашение о вызовах __vectorcall для архитектуры x64

Соглашение __vectorcall о вызове x64 расширяет стандартное соглашение о вызовах x64, чтобы воспользоваться дополнительными регистрами. Аргументы как целочисленного, так и векторного типа сопоставляются с регистрами исходя из позиции в списке аргументов. Аргументы HVA назначаются неиспользуемым векторным регистрам.

Если любые из первых четырех аргументов в порядке слева направо являются целочисленными, они передаются в регистрах, соответствующих их позициям: RCX, RDX, R8 или R9. Скрытый this указатель рассматривается как первый аргумент целочисленного типа. Если аргумент HVA в одном из первых четырех аргументов не может быть передан в доступных регистрах, ссылка на выделенную вызывающим память передается в соответствующем регистре целочисленного типа. Аргументы целочисленного типа, расположенные за четвертой позицией в списке параметров, передаются в стеке.

Если любые из первых шести аргументов в порядке слева направо являются аргументами векторного типа, они передаются по значению в векторных регистрах SSE 0–5, соответствующих их позициям. С плавающей запятой и __m128 типы передаются в регистрах XMM, а __m256 типы передаются в регистрах YMM. В этом состоит отличие от стандартного соглашения о вызовах x64, так как векторные типы передаются по значению, а не по ссылкам; кроме того, используются дополнительные регистры. Пространство теневого стека, выделенное для аргументов типа вектора, исправлено на 8 байт, и /homeparams параметр не применяется. Аргументы векторного типа начиная с седьмой позиции в списке параметров передаются в стеке по ссылке на память, выделенную вызывающим объектом.

После выделения регистров для векторных аргументов члены данных аргументов HVA выделяются в порядке возрастания для неиспользуемых регистров векторов XMM0 до XMM5 (или YMM0 до YMM5 для __m256 типов), если для всего HVA достаточно регистров. Если регистров недостаточно, аргумент HVA передается по ссылке на память, выделенную вызывающим объектом. Пространство теневого стека, выделенное для аргументов типа HVA, имеет фиксированный размер 8 байт с неопределенным содержимым. Аргументы HVA назначаются регистрам в порядке слева направо в списке параметров и могут находиться в любой позиции. Аргументы HVA, находящиеся на одной из первых четырех позиций в списке аргументов и не назначенные векторным регистрам, передаются по ссылке в целочисленном регистре, соответствующем их позициям. Аргументы HVA, переданные по ссылке после четвертой позиции в списке параметров, помещаются в стек.

Результаты функций возвращаются по значению __vectorcall в регистрах, когда это возможно. Результаты целочисленных типов, включая struct и union размером 8 байт и менее, возвращаются по значению в RAX. Результаты векторного типа возвращаются по значению в XMM0 или YMM0 в зависимости от их размера. В результатах HVA каждый элемент данных возвращается по значению в регистрах XMM0:XMM3 или YMM0:YMM3 в зависимости от размера элемента. Типы результатов, которые не помещаются в соответствующее регистры, возвращаются по ссылке на память, выделенную вызывающим объектом.

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

Примеры:

// crt_vc64.c
// Build for amd64 with: cl /arch:AVX /W3 /FAs crt_vc64.c
// This example creates an annotated assembly listing in
// crt_vc64.asm.

#include <intrin.h>
#include <xmmintrin.h>

typedef struct {
   __m128 array[2];
} hva2;    // 2 element HVA type on __m128

typedef struct {
   __m256 array[4];
} hva4;    // 4 element HVA type on __m256

// Example 1: All vectors
// Passes a in XMM0, b in XMM1, c in YMM2, d in XMM3, e in YMM4.
// Return value in XMM0.
__m128 __vectorcall
example1(__m128 a, __m128 b, __m256 c, __m128 d, __m256 e) {
   return d;
}

// Example 2: Mixed int, float and vector parameters
// Passes a in RCX, b in XMM1, c in R8, d in XMM3, e in YMM4,
// f in XMM5, g pushed on stack.
// Return value in YMM0.
__m256 __vectorcall
example2(int a, __m128 b, int c, __m128 d, __m256 e, float f, int g) {
   return e;
}

// Example 3: Mixed int and HVA parameters
// Passes a in RCX, c in R8, d in R9, and e pushed on stack.
// Passes b by element in [XMM0:XMM1];
// b's stack shadow area is 8-bytes of undefined value.
// Return value in XMM0.
__m128 __vectorcall example3(int a, hva2 b, int c, int d, int e) {
   return b.array[0];
}

// Example 4: Discontiguous HVA
// Passes a in RCX, b in XMM1, d in XMM3, and e is pushed on stack.
// Passes c by element in [YMM0,YMM2,YMM4,YMM5], discontiguous because
// vector arguments b and d were allocated first.
// Shadow area for c is an 8-byte undefined value.
// Return value in XMM0.
float __vectorcall example4(int a, float b, hva4 c, __m128 d, int e) {
   return b;
}

// Example 5: Multiple HVA arguments
// Passes a in RCX, c in R8, e pushed on stack.
// Passes b in [XMM0:XMM1], d in [YMM2:YMM5], each with
// stack shadow areas of an 8-byte undefined value.
// Return value in RAX.
int __vectorcall example5(int a, hva2 b, int c, hva4 d, int e) {
   return c + e;
}

// Example 6: HVA argument passed by reference, returned by register
// Passes a in [XMM0:XMM1], b passed by reference in RDX, c in YMM2,
// d in [XMM3:XMM4].
// Register space was insufficient for b, but not for d.
// Return value in [YMM0:YMM3].
hva4 __vectorcall example6(hva2 a, hva4 b, __m256 c, hva2 d) {
   return b;
}

int __cdecl main( void )
{
   hva4 h4;
   hva2 h2;
   int i;
   float f;
   __m128 a, b, d;
   __m256 c, e;

   a = b = d = _mm_set1_ps(3.0f);
   c = e = _mm256_set1_ps(5.0f);
   h2.array[0] = _mm_set1_ps(6.0f);
   h4.array[0] = _mm256_set1_ps(7.0f);

   b = example1(a, b, c, d, e);
   e = example2(1, b, 3, d, e, 6.0f, 7);
   d = example3(1, h2, 3, 4, 5);
   f = example4(1, 2.0f, h4, d, 5);
   i = example5(1, h2, 3, h4, 5);
   h4 = example6(h2, h4, c, h2);
}

Соглашение о вызовах __vectorcall для архитектуры x86

Соглашение __vectorcall о вызове __fastcall следует соглашению для 32-разрядных аргументов целочисленного типа и использует регистры векторов SSE для типов векторов и аргументов HVA.

Первые два целочисленных аргумента, встреченные в списке параметров в порядке слева направо, помещаются в регистры ECX и EDX соответственно. Скрытый this указатель обрабатывается как первый аргумент целочисленного типа и передается в ECX. Первые шесть аргументов векторного типа передаются по значению через векторные регистры SSE 0–5 в регистры XMM или YMM в зависимости от размера аргумента.

Первые шесть аргументов векторного типа в порядке слева направо передаются по значению в векторных регистрах SSE 0–5. С плавающей запятой и __m128 типы передаются в регистрах XMM, а __m256 типы передаются в регистрах YMM. Для аргументов векторного типа, которые передаются через регистр, пространство теневого стека не выделяется. Аргументы векторного типа начиная с седьмой позиции в списке передаются в стек по ссылке на память, выделенную вызывающим объектом. Ограничение ошибки компилятора C2719 не применяется к этим аргументам.

После выделения регистров для векторных аргументов элементы данных аргументов HVA выделяются в порядке возрастания для неиспользуемых регистров векторов XMM0 до XMM5 (или YMM0 до YMM5 для __m256 типов), если для всего HVA достаточно регистров. Если регистров недостаточно, аргумент HVA передается в стек по ссылке на память, выделенную вызывающим объектом. Пространство теневого стека аргументу HVA не выделяется. Аргументы HVA назначаются регистрам в порядке слева направо в списке параметров и могут находиться в любой позиции.

Результаты функций возвращаются по значению __vectorcall в регистрах, когда это возможно. Результаты целочисленных типов, включая structs и union размером 4 байт и менее, возвращаются по значению в EAX. Структуры или объединения целочисленного типа размером 8 байт или менее возвращаются по значению в регистрах EDX:EAX. Результаты векторного типа возвращаются по значению в XMM0 или YMM0 в зависимости от их размера. В результатах HVA каждый элемент данных возвращается по значению в регистрах XMM0:XMM3 или YMM0:YMM3 в зависимости от размера элемента. Результаты других типов возвращаются по ссылке на память, выделенную вызывающим объектом.

Реализация x86 __vectorcall соответствует соглашению аргументов, отправленных в стек справа налево вызывающей стороной, и вызываемая функция очищает стек непосредственно перед возвратом. В стек передаются только те аргументы, которые не помещаются в регистры.

Примеры:

// crt_vc86.c
// Build for x86 with: cl /arch:AVX /W3 /FAs crt_vc86.c
// This example creates an annotated assembly listing in
// crt_vc86.asm.

#include <intrin.h>
#include <xmmintrin.h>

typedef struct {
   __m128 array[2];
} hva2;    // 2 element HVA type on __m128

typedef struct {
   __m256 array[4];
} hva4;    // 4 element HVA type on __m256

// Example 1: All vectors
// Passes a in XMM0, b in XMM1, c in YMM2, d in XMM3, e in YMM4.
// Return value in XMM0.
__m128 __vectorcall
example1(__m128 a, __m128 b, __m256 c, __m128 d, __m256 e) {
   return d;
}

// Example 2: Mixed int, float and vector parameters
// Passes a in ECX, b in XMM0, c in EDX, d in XMM1, e in YMM2,
// f in XMM3, g pushed on stack.
// Return value in YMM0.
__m256 __vectorcall
example2(int a, __m128 b, int c, __m128 d, __m256 e, float f, int g) {
   return e;
}

// Example 3: Mixed int and HVA parameters
// Passes a in ECX, c in EDX, d and e pushed on stack.
// Passes b by element in [XMM0:XMM1].
// Return value in XMM0.
__m128 __vectorcall example3(int a, hva2 b, int c, int d, int e) {
   return b.array[0];
}

// Example 4: HVA assigned after vector types
// Passes a in ECX, b in XMM0, d in XMM1, and e in EDX.
// Passes c by element in [YMM2:YMM5].
// Return value in XMM0.
float __vectorcall example4(int a, float b, hva4 c, __m128 d, int e) {
   return b;
}

// Example 5: Multiple HVA arguments
// Passes a in ECX, c in EDX, e pushed on stack.
// Passes b in [XMM0:XMM1], d in [YMM2:YMM5].
// Return value in EAX.
int __vectorcall example5(int a, hva2 b, int c, hva4 d, int e) {
   return c + e;
}

// Example 6: HVA argument passed by reference, returned by register
// Passes a in [XMM1:XMM2], b passed by reference in ECX, c in YMM0,
// d in [XMM3:XMM4].
// Register space was insufficient for b, but not for d.
// Return value in [YMM0:YMM3].
hva4 __vectorcall example6(hva2 a, hva4 b, __m256 c, hva2 d) {
   return b;
}

int __cdecl main( void )
{
   hva4 h4;
   hva2 h2;
   int i;
   float f;
   __m128 a, b, d;
   __m256 c, e;

   a = b = d = _mm_set1_ps(3.0f);
   c = e = _mm256_set1_ps(5.0f);
   h2.array[0] = _mm_set1_ps(6.0f);
   h4.array[0] = _mm256_set1_ps(7.0f);

   b = example1(a, b, c, d, e);
   e = example2(1, b, 3, d, e, 6.0f, 7);
   d = example3(1, h2, 3, 4, 5);
   f = example4(1, 2.0f, h4, d, 5);
   i = example5(1, h2, 3, h4, 5);
   h4 = example6(h2, h4, c, h2);
}

End Microsoft Specific

См. также

Передача аргументов и соглашения об именовании
Ключевые слова