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


__vectorcall

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

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

Элемент

Реализация

Соглашение об оформлении имен C

К именам функций добавляется суффикс @@, после которого указывается количество байтов (в десятичном формате) в списке параметров.

Соглашение о преобразовании регистра

Преобразование регистра не выполняется.

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

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

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

Векторный тип — это либо тип с плавающей запятой, например float или double, либо векторный тип SIMD, например __m128 или __m256.

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

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

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

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

На компьютерах ARM соглашение о вызовах __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 для архитектуры 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 в зависимости от размера элемента. Типы результатов, которые не помещаются в соответствующее регистры, возвращаются по ссылке на память, выделенную вызывающим объектом.

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

Примеры

// 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 в зависимости от размера элемента. Результаты других типов возвращаются по ссылке на память, выделенную вызывающим объектом.

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

Примеры

// 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);
}

Завершение блока, относящегося только к системам Microsoft

См. также

Ссылки

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

Ключевые слова в C++