MinGW 9.2.0. Error: the call requires 'ifunc', which is not supported by this target
Решил попытаться освоить такую возможность компилятора GCC, как FMV (function multiversioning). Если я правильно понял, то этот механизм позволяет в рантайме определять, поддерживает ли CPU тот или иной набор инструкций и вызывать соответствующие версии функций. Написал простенькую программу для эксперимента, которая копирует один блок данных типа int в другой средствами avx, sse и обычного x86-кода:
#include <stdio.h>
#include <assert.h>
#include <x86intrin.h>
#define INLINE __attribute__ ((always_inline)) inline
#define NOINLINE __attribute__ ((noinline,noclone))
void move_block(int* pDst, int* pSrc, size_t blockLen);
void show_block(int* pBlock, size_t blockLen);
alignas(32) int src[] = {11, 22, 33, 44, 55, 66, 77, 88, 10, 20, 30, 40, 50, 60, 70, 80, 17, 18, 21, 22, 23, 32};
#define LEN (sizeof(src)/sizeof(src[0]))
alignas(32) int dst[LEN];
__attribute__ ((target_clones ("avx,sse,default")))
int app_main()
{
move_block(dst, src, LEN);
show_block(dst, LEN);
return 0;
}
int main()
{
return app_main();
}
__attribute__ ((target ("avx")))
NOINLINE void move_block(int* pDst, int* pSrc, size_t blockLen)
{
assert((((size_t)pDst)&0x1F) == 0);
assert((((size_t)pSrc)&0x1F) == 0);
int rem = blockLen&7;
int* pDstEnd = pDst + (blockLen&(~7));
while (pDst<pDstEnd)
{
_mm256_store_si256((__m256i*)pDst, _mm256_load_si256((__m256i*)pSrc));
pDst+= 8;
pSrc+= 8;
}
if (rem > 3)
{
rem-= 4;
_mm_store_si128((__m128i*)pDst, _mm_load_si128((__m128i*)pSrc));
pDst+= 4;
pSrc+= 4;
}
while (rem)
{
*(pDst++) = *(pSrc++);
--rem;
}
}
__attribute__ ((target ("sse")))
NOINLINE void move_block(int* pDst, int* pSrc, size_t blockLen)
{
assert((((size_t)pDst)&0xF) == 0);
assert((((size_t)pSrc)&0xF) == 0);
int rem = blockLen&3;
int* pDstEnd = pDst + (blockLen&(~3));
while (pDst<pDstEnd)
{
_mm_store_si128((__m128i*)pDst, _mm_load_si128((__m128i*)pSrc));
pDst+= 4;
pSrc+= 4;
}
while (rem)
{
*(pDst++) = *(pSrc++);
--rem;
}
}
__attribute__ ((target ("default")))
NOINLINE void move_block(int* pDst, int* pSrc, size_t blockLen)
{
for (int i=0; i<blockLen; ++i)
pDst[i] = pSrc[i];
}
NOINLINE void show_block(int* pBlock, size_t blockLen)
{
assert (blockLen != 0);
printf("%d: {", blockLen);
for (size_t i=0; i<blockLen-1; ++i) printf("%d, ", pBlock[i]);
printf("%d}\n", pBlock[blockLen-1]);
}
И со старта получаю ошибку:
main.cpp:19:5: error: the call requires 'ifunc', which is not supported by this target
19 | int app_main()
| ^~~~~~~~
Компилирую с опциями: -fno-rtti -O3 -march=native -mtune=i386, однако вариации с -march и -mtune результатов не дают.
Как я понял, в ответ на __attribute__ ((target_clones ("avx,sse,default"))) компилятор должен сгенерировать три версии функции app_main(), каждая из которых, соответственно, должна вызывать нужную версию move_block(), а в стартап помещать резолвер, инициализирующий указатель на актуальную для конкретного CPU версию app_main() (и вызов этой функции должен происходить через указатель). Но видимо я что-то понял не так. Вопрос: что именно я упустил?
Также хочу заметить, что если строку __attribute__ ((target_clones ("avx,sse,default"))) закоментировать, то код компилируется успешно, однако с прямым вызовом AVX-версии функции move_block() - т.е. не о какой обратной совмеестимости с i386 речи не идёт:
.text:00404254 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00404254 public _main
.text:00404254 _main proc near ; CODE XREF: sub_4011A0+8E↑p
.text:00404254
.text:00404254 argc = dword ptr 8
.text:00404254 argv = dword ptr 0Ch
.text:00404254 envp = dword ptr 10h
.text:00404254
.text:00404254 ; __unwind {
.text:00404254 push ebp
.text:00404255 mov ebp, esp
.text:00404257 and esp, 0FFFFFFF0h
.text:0040425A call ___main
.text:0040425F call __Z8app_mainv ; app_main(void)
.text:00404264 leave
.text:00404265 retn
.text:00404265 ; } // starts at 404254
.text:00404265 _main endp
.text:00401678 ; _DWORD app_main(void)
.text:00401678 public __Z8app_mainv
.text:00401678 __Z8app_mainv proc near ; CODE XREF: _main+B↓p
.text:00401678
.text:00401678 var_1C = dword ptr -1Ch
.text:00401678 var_18 = dword ptr -18h
.text:00401678 var_14 = dword ptr -14h
.text:00401678
.text:00401678 ; __unwind {
.text:00401678 sub esp, 1Ch
.text:0040167B mov [esp+1Ch+var_14], 16h
.text:00401683 mov [esp+1Ch+var_18], offset _src
.text:0040168B mov [esp+1Ch+var_1C], offset _dst
.text:00401692 call __Z10move_blockPiS_j_avx
.text:00401697 mov [esp+1Ch+var_18], 16h ; unsigned int
.text:0040169F mov [esp+1Ch+var_1C], offset _dst ; char *
.text:004016A6 call __Z10show_blockPij ; show_block(int *,uint)
.text:004016AB xor eax, eax
.text:004016AD add esp, 1Ch
.text:004016B0 retn
.text:004016B0 ; } // starts at 401678
.text:004016B0 __Z8app_mainv endp
Ответы (1 шт):
Проверил на MSYS2 GCC 11.3 - та же ошибка.
Зато Clang-ом собралось с небольшими изменениями. Вывод - GCC в топку?
Убрал noclone, noinline, добавил alignas, поставил вызовы функций ниже определений (альтернатива - навесить атрибуты на объявления тоже).
#include <stdio.h>
#include <assert.h>
#include <x86intrin.h>
#define INLINE __attribute__ ((always_inline)) inline
alignas(32) int src[] = {11, 22, 33, 44, 55, 66, 77, 88, 10, 20, 30, 40, 50, 60, 70, 80, 17, 18, 21, 22, 23, 32};
#define LEN (sizeof(src)/sizeof(src[0]))
alignas(32) int dst[LEN];
__attribute__ ((target ("avx")))
void move_block(int* pDst, int* pSrc, size_t blockLen)
{
assert((((size_t)pDst)&0x1F) == 0);
assert((((size_t)pSrc)&0x1F) == 0);
int rem = blockLen&7;
int* pDstEnd = pDst + (blockLen&(~7));
while (pDst<pDstEnd)
{
_mm256_store_si256((__m256i*)pDst, _mm256_load_si256((__m256i*)pSrc));
pDst+= 8;
pSrc+= 8;
}
if (rem > 3)
{
rem-= 4;
_mm_store_si128((__m128i*)pDst, _mm_load_si128((__m128i*)pSrc));
pDst+= 4;
pSrc+= 4;
}
while (rem)
{
*(pDst++) = *(pSrc++);
--rem;
}
}
__attribute__ ((target ("sse")))
void move_block(int* pDst, int* pSrc, size_t blockLen)
{
assert((((size_t)pDst)&0xF) == 0);
assert((((size_t)pSrc)&0xF) == 0);
int rem = blockLen&3;
int* pDstEnd = pDst + (blockLen&(~3));
while (pDst<pDstEnd)
{
_mm_store_si128((__m128i*)pDst, _mm_load_si128((__m128i*)pSrc));
pDst+= 4;
pSrc+= 4;
}
while (rem)
{
*(pDst++) = *(pSrc++);
--rem;
}
}
__attribute__ ((target ("default")))
void move_block(int* pDst, int* pSrc, size_t blockLen)
{
for (int i=0; i<blockLen; ++i)
pDst[i] = pSrc[i];
}
void show_block(int* pBlock, size_t blockLen)
{
assert (blockLen != 0);
printf("%d: {", blockLen);
for (size_t i=0; i<blockLen-1; ++i) printf("%d, ", pBlock[i]);
printf("%d}\n", pBlock[blockLen-1]);
}
__attribute__ ((target_clones ("avx,sse,default")))
int app_main()
{
move_block(dst, src, LEN);
show_block(dst, LEN);
return 0;
}
int main()
{
return app_main();
}