__vectorcall

Specyficzne dla firmy Microsoft

Konwencja __vectorcall wywoływania określa, że argumenty funkcji mają być przekazywane w rejestrach, gdy jest to możliwe. __vectorcall używa większej liczby rejestrów dla argumentów lub __fastcall domyślnego użycia konwencji wywoływania x64. Konwencja __vectorcall wywoływania jest obsługiwana tylko w kodzie natywnym na procesorach x86 i x64, które obejmują rozszerzenia SIMD 2 (SSE2) i nowsze. Służy __vectorcall do przyspieszania funkcji, które przekazują kilka argumentów wektorów zmiennoprzecinkowych lub SIMD i wykonują operacje korzystające z argumentów załadowanych w rejestrach. Na poniższej liście przedstawiono funkcje wspólne dla implementacji x86 i x64 programu __vectorcall. Różnice zostały wyjaśnione w dalszej części tego artykułu.

Element Implementacja
Konwencja dekorowania nazw języka C Nazwy funkcji są sufiksem z dwoma znakami "at" (@@), po których następuje liczba bajtów (w liczbach dziesiętnych) na liście parametrów.
Konwencja translacji wielkości liter Nie jest wykonywane tłumaczenie wielkości liter.

Użycie opcji kompilatora /Gv powoduje, że każda funkcja w module jest kompilowana jako __vectorcall , chyba że funkcja jest funkcją składową, jest zadeklarowana z atrybutem konwencji wywoływania powodującego konflikt, używa vararg listy argumentów zmiennej lub ma nazwę main.

Można przekazać trzy rodzaje argumentów, rejestrując się w __vectorcall funkcjach: wartości typu całkowitego, wartości typu wektora i homogeniczne wartości agregacji wektorów (HVA).

Typ liczby całkowitej spełnia dwa wymagania: pasuje do natywnego rozmiaru rejestru procesora — na przykład 4 bajty na maszynie x86 lub 8 bajtów na maszynie x64 — i jest konwertowany na liczbę całkowitą długości rejestru i z powrotem bez zmiany reprezentacji bitowej. Na przykład dowolny typ, który można awansować do int wartości na x86 (long long na x64) — na przykład typu char lub short— lub , który można rzutować na int (long long na x64) i z powrotem do oryginalnego typu bez zmiany, jest typem całkowitym. Typy liczb całkowitych obejmują wskaźnik, odwołanie i structunion typy 4 bajtów (8 bajtów w x64) lub mniejsze. Na platformach x64 większe struct i union typy są przekazywane przez odwołanie do pamięci przydzielonej przez obiekt wywołujący; na platformach x86 są przekazywane przez wartość na stosie.

Typ wektora jest typem zmiennoprzecinkowa — na przykład typem wektora float SIMD lub double, na przykład __m128 lub __m256.

Typ HVA to złożony typ maksymalnie czterech składowych danych, które mają identyczne typy wektorów. Typ HVA ma takie samo wymaganie wyrównania, jak typ wektora jego składowych. Jest to przykład definicji HVA struct , która zawiera trzy identyczne typy wektorów i ma wyrównanie 32 bajtów:

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

Zadeklaruj funkcje jawnie za pomocą słowa kluczowego __vectorcall w plikach nagłówków, aby umożliwić oddzielnie skompilowany kod do łączenia bez błędów. Funkcje muszą być prototypowane do używania __vectorcallelementu i nie mogą używać listy argumentów o zmiennej vararg długości.

Funkcję składową można zadeklarować przy użyciu specyfikatora __vectorcall . Ukryty this wskaźnik jest przekazywany przez rejestr jako pierwszy argument typu całkowitego.

Na maszynach __vectorcall arm jest akceptowany i ignorowany przez kompilator. W ARM64EC __vectorcall jest nieobsługiwany i odrzucany przez kompilator.

W przypadku funkcji składowych niestacjonanych, jeśli funkcja jest zdefiniowana poza wierszem, modyfikator konwencji wywołującej nie musi być określony w definicji poza wierszem. Oznacza to, że w przypadku składowych niestacjonanych klas konwencja wywoływania określona podczas deklaracji jest zakładana w punkcie definicji. Biorąc pod uwagę tę definicję klasy:

struct MyClass {
   void __vectorcall mymethod();
};

to:

void MyClass::mymethod() { return; }

jest równoważne temu:

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

Modyfikator __vectorcall konwencji wywoływania musi być określony podczas tworzenia wskaźnika do __vectorcall funkcji. W następnym przykładzie zostanie typedef utworzony wskaźnik __vectorcall do funkcji, która przyjmuje cztery double argumenty i zwraca __m256 wartość:

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

Aby uzyskać zgodność z poprzednimi wersjami, jest synonimem, _vectorcall__vectorcall chyba że określono opcję /Za kompilatora (Wyłącz rozszerzenia języka).

konwencja __vectorcall na x64

Konwencja wywołująca __vectorcall x64 rozszerza standardową konwencję wywoływania x64 w celu skorzystania z dodatkowych rejestrów. Argumenty typu liczba całkowita i argumenty typu wektora są mapowane na rejestry na podstawie pozycji na liście argumentów. Argumenty HVA są przydzielane do nieużywanych rejestrów wektorów.

Jeśli każdy z pierwszych czterech argumentów w kolejności od lewej do prawej to argumenty typu liczba całkowita, są przekazywane w rejestrze odpowiadającym tej pozycji — RCX, RDX, R8 lub R9. Ukryty this wskaźnik jest traktowany jako pierwszy argument typu liczby całkowitej. Jeśli argument HVA w jednym z pierwszych czterech argumentów nie może zostać przekazany w dostępnych rejestrach, zamiast tego zostanie przekazane odwołanie do pamięci przydzielonej przez obiekt wywołujący w odpowiednim rejestrze typu całkowitego. Argumenty typu liczba całkowita po przekazaniu czwartej pozycji parametru na stosie.

Jeśli dowolny z pierwszych sześciu argumentów w kolejności od lewej do prawej to argumenty typu wektorowego, są przekazywane przez wartość w rejestrach wektorów SSE od 0 do 5 zgodnie z pozycją argumentu. Zmiennoprzecinkowe i __m128 typy są przekazywane w rejestrach XMM, a __m256 typy są przekazywane w rejestrach YMM. Różni się to od standardowej konwencji wywoływania x64, ponieważ typy wektorów są przekazywane przez wartość zamiast odwołania, a dodatkowe rejestry są używane. Miejsce stosu w tle przydzielone dla argumentów typu wektora jest stałe na 8 bajtów, a /homeparams opcja nie ma zastosowania. Argumenty typu wektorowego w siódmych i nowszych pozycjach parametrów są przekazywane na stos przez odwołanie do pamięci przydzielonej przez obiekt wywołujący.

Po przydzieleniu rejestrów dla argumentów wektorowych, składowe danych argumentów HVA są przydzielane w kolejności rosnącej do nieużywanych rejestrów wektorów XMM0 do XMM5 (lub YMM0 do YMM5, dla __m256 typów), o ile jest wystarczająca ilość rejestrów dostępnych dla całego HVA. Jeśli nie są dostępne wystarczające rejestry, argument HVA jest przekazywany przez odwołanie do pamięci przydzielonej przez obiekt wywołujący. Miejsce w cieniu stosu dla argumentu HVA jest stałe w 8 bajtach z niezdefiniowaną zawartością. Argumenty HVA są przypisywane do rejestrów w kolejności od lewej do prawej na liście parametrów i mogą znajdować się w dowolnej pozycji. Argumenty HVA w jednym z pierwszych czterech pozycji argumentów, które nie są przypisane do rejestrów wektorów, są przekazywane przez odwołanie w rejestrze liczb całkowitych odpowiadających tej pozycji. Argumenty HVA przekazywane przez odwołanie po czwartej pozycji parametru są wypychane na stos.

__vectorcall Wyniki funkcji są zwracane przez wartość w rejestrach, gdy jest to możliwe. Wyniki typu liczby całkowitej, w tym struktury lub unii o wartości 8 bajtów lub mniejszej, są zwracane przez wartość w rax. Wyniki typu wektorowego są zwracane przez wartość w programie XMM0 lub YMM0 w zależności od rozmiaru. Wyniki HVA mają każdy element danych zwracany przez wartość w rejestrach XMM0:XMM3 lub YMM0:YMM3, w zależności od rozmiaru elementu. Typy wyników, które nie mieszczą się w odpowiednich rejestrach, są zwracane przez odwołanie do pamięci przydzielonej przez obiekt wywołujący.

Stos jest obsługiwany przez obiekt wywołujący w implementacji x64 klasy __vectorcall. Prolog wywołujący i kod epilogu przydziela i czyści stos dla wywoływanej funkcji. Argumenty są wypychane na stos od prawej do lewej, a miejsce stosu w tle jest przydzielane dla argumentów przekazywanych w rejestrach.

Przykłady:

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

konwencja __vectorcall na x86

Konwencja __vectorcall wywoływania jest zgodna z konwencją __fastcall argumentów typu 32-bitowego typu całkowitego i korzysta z rejestrów wektorów SSE dla typu wektora i argumentów HVA.

Dwa pierwsze argumenty typu całkowitego znalezione na liście parametrów od lewej do prawej znajdują się odpowiednio w ecX i EDX. Ukryty this wskaźnik jest traktowany jako pierwszy argument typu liczby całkowitej i jest przekazywany w ecX. Pierwsze sześć argumentów typu wektorowego jest przekazywanych przez wartość za pośrednictwem rejestrów wektorów SSE od 0 do 5 w rejestrach XMM lub YMM, w zależności od rozmiaru argumentu.

Pierwsze sześć argumentów typu wektorów w kolejności od lewej do prawej jest przekazywanych przez wartość w rejestrach wektorów SSE od 0 do 5. Zmiennoprzecinkowe i __m128 typy są przekazywane w rejestrach XMM, a __m256 typy są przekazywane w rejestrach YMM. Nie przydzielono miejsca na stos w tle dla argumentów typu wektorowego przekazywanych przez rejestr. Argumenty siódmego i późniejszego typu wektora są przekazywane na stos przez odwołanie do pamięci przydzielonej przez obiekt wywołujący. Ograniczenie błędu kompilatora C2719 nie ma zastosowania do tych argumentów.

Po przydzieleniu rejestrów dla argumentów wektorowych elementy członkowskie argumentów HVA są przydzielane w kolejności rosnącej do nieużywanych rejestrów wektorów XMM0 do XMM5 (lub YMM0 do YMM5 dla __m256 typów), o ile dla całego HVA jest dostępna wystarczająca liczba rejestrów. Jeśli nie są dostępne wystarczające rejestry, argument HVA jest przekazywany na stos przez odwołanie do pamięci przydzielonej przez obiekt wywołujący. Nie przydzielono miejsca w cieniu stosu dla argumentu HVA. Argumenty HVA są przypisywane do rejestrów w kolejności od lewej do prawej na liście parametrów i mogą znajdować się w dowolnej pozycji.

__vectorcall Wyniki funkcji są zwracane przez wartość w rejestrach, gdy jest to możliwe. Wyniki typu liczby całkowitej, w tym struktury lub unii o wartości 4 bajtów lub mniejszej, są zwracane przez wartość w programie EAX. Struktury typu liczba całkowita lub unii 8 bajtów lub mniej są zwracane przez wartość w EDX:EAX. Wyniki typu wektorowego są zwracane przez wartość w programie XMM0 lub YMM0 w zależności od rozmiaru. Wyniki HVA mają każdy element danych zwracany przez wartość w rejestrach XMM0:XMM3 lub YMM0:YMM3, w zależności od rozmiaru elementu. Inne typy wyników są zwracane przez odwołanie do pamięci przydzielonej przez obiekt wywołujący.

Implementacja x86 jest zgodna z __vectorcall konwencją argumentów wypychanych na stos od prawej do lewej przez obiekt wywołujący, a wywołana funkcja czyści stos tuż przed jego zwróceniem. Na stosie są wypychane tylko argumenty, które nie są umieszczane w rejestrach.

Przykłady:

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

Koniec specyficzny dla firmy Microsoft

Zobacz też

Przekazywanie argumentów i konwencje nazewnictwa
Słowa kluczowe