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 шт):

Автор решения: HolyBlackCat

Проверил на 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();
}
→ Ссылка