GCC和Clang都不会通过编译时已知的函数指针数组内联调用 - 为什么?

时间:2017-06-18 10:48:59

标签: c++ gcc

Compiler Explorer上的示例代码:https://godbolt.org/g/fPfw4k

我试图使用函数指针数组作为跳转表而不是开关,因为我觉得它更干净。然而,令我惊讶的是,GCC和Clang编译器似乎都无法强调这一点。

有具体原因吗?

示例代码包含死链接:

namespace{
template<int N>
int bar(){
    return N;
}

int foo1(int n){
     if(n < 0 || n > 5){
        __builtin_unreachable();
    }
    #if __clang__
    __builtin_assume(n >= 0 && n <= 5);
    #endif
    static int (* const fns[])() = {
        bar<0>, bar<1>, bar<2>, bar<3>, bar<4>, bar<5>
    };
    return fns[n]();
}

int foo2(int n){
    #if __clang__
    __builtin_assume(n >= 0 && n <= 5);
    #endif
    switch(n){
        case 0:
            return bar<0>();
        case 1:
            return bar<1>();
        case 2:
            return bar<2>();
        case 3:
            return bar<3>();
        case 4:
            return bar<4>();
        case 5:
            return bar<5>();
        default:
            __builtin_unreachable();
    }
}
}

int main(int argc, char** argv){
    volatile int n = foo1(argc);
    volatile int p = foo2(argc);
}

使用GCC&amp; amp;提供的语言扩展属性always_inline。 Clang也没什么区别。

1 个答案:

答案 0 :(得分:0)

编译器无法内联foo1中的调用,因为调用不使用编译时常量callee。如果它知道在编译时通过内联将一个常量参数传递给foo1,它将内联正确的函数。

考虑这个例子:

namespace{
template<int N>
int bar(){
    return N;
}

int foo1(int n){
     if(n < 0 || n > 5){
        __builtin_unreachable();
    }
    #if __clang__
    __builtin_assume(n >= 0 && n <= 5);
    #endif
    static int (* const fns[])() = {
        bar<0>, bar<1>, bar<2>, bar<3>, bar<4>, bar<5>
    };
    return fns[n]();
}
}

int main(int argc, char** argv){
    int n = foo1(3);

    return n;
}

两个编译器都将其编译为以下代码:

main:
        mov     eax, 3
        ret

在foo2的情况下,编译器以5个不同的调用方式启动,其中包含常量callees,所有这些调用都是内联的。然后它进一步优化生成的代码,如果它认为有利可图,则生成自己的跳转表。

我想编译器可能会尝试从跳转表中提取一个开关,然后内联所有内容,但是这将非常复杂,并且在一般情况下不太可能产生性能提升,因此gcc和clang似乎都没有这样做