代码之家  ›  专栏  ›  技术社区  ›  TheHans255

在LLVM中,您能指定两个不同的代码路径将产生相同的结果吗?

  •  1
  • TheHans255  · 技术社区  · 2 年前

    我正在为嵌入式平台编写一个C库,该平台的固件有几个用于字符输出的例程。其中一个子例程是通用的——将要打印的ASCII字符加载到寄存器中,调用该子例程,然后打印出该字符。然而,其他一些子例程跳过寄存器加载并输出特定的控制字符,如钟形字符和回车。

    在我的 putchar() 实施(其中 printf 使用),我可能想将这些例程中的每一个用于不同的字符:

    void putchar(char c) {
        if (__builtin_expect(c == '\n', 0)) call_line_feed_out();
        else if (__builtin_expect(c == '\a', 0)) call_bell_out();
        else call_cout(c);
    }
    

    编译时 输出函数 对于通用输入,我希望LLVM进行优化 普查尔() 直接到 call_cout 并跳过分支。然而,在LLVM能够将所有内容编译为 putchar 调用(例如使用足够小的常量字符串打印),我希望LLVM使用这些特殊的端点来节省内存。

    我想,如果LLVM知道这一点,它自然会这么做,比如 call_line_feed_out() call_cout('\n') 做了同样的事情,它会为任何一个选择最佳的代码路径。这是我能做的事情吗?还是我需要进一步进行这种差异化(例如 输出函数 呼叫 调用_输出 直接地

    (请注意,这些的主体 call_ 函数是 asm volatile 进行固件调用的语句,而LLVM无法访问这些调用的列表。不过,如果可以的话,我手头确实有房源,而且 能够 以某种方式将这些信息提供给LLVM,这样它就可以自己做出决定。)

    0 回复  |  直到 2 年前
        1
  •  1
  •   Peter Cordes    2 年前

    我不知道有什么方法可以告诉编译器它可以进行不同的调用,但在这种情况下,我们可以用不同的方式实现相同的目标。

    只对编译时常数使用特殊情况的常用方法是 __builtin_constant_p() 在内联+常量折叠之后,测试变量的值在编译时是否已知。(老Clang用来评估 __内置常量p() 太早了,在内联之前,所以函数参数永远不会是常量,这使得它除了在宏中之外有些无用。但这已经被修复,使其发挥作用 like GCC intended .)

    这个 _p GCC内置程序的命名 comes from Lisp 。这是一个“谓词”函数,它询问一个是/否问题并返回一个布尔值。

    void my_putchar(char c) {
        if (__builtin_constant_p(c)) {
            // only reached if c is a compile-time constant after inlining
            if (c == '\n') { call_line_feed_out(); return; }
            else if (c == '\a') { call_bell_out(); return; }
        }
        // else runtime variable or a constant that wasn't one of those special cases
        call_cout(c);
    }
    

    的非内联定义 my_putchar 只是对 call_cout 。(如果我们能以某种方式将该符号作为的别名,那就更好了 调用_输出 因此执行不必通过这一个指令函数。)

    或者这只是一个常规的电话 调用_输出 当与非常量内联时,或与除之外的字符内联时 '\n' '\a' .

    void use_putchar_newline(void){
        my_putchar('\n');
    }
    

    编译为 b call_line_feed_out -尾随。

    void use_putchar_test(char *str){
        my_putchar(str[0]);
        my_putchar(str[1]);
        my_putchar(str[2]);
        const char *strconst = "ab\n";
        my_putchar(strconst[0]);
        my_putchar(strconst[1]);
        my_putchar(strconst[2]);
    }
    

    为AArch64编译 clang on Godbolt -O3 -fomit-frame-pointer 。(我想 -fomit-frame-pointer 默认情况下将在打开 -O3 对于Linux,但显然不是。)

    use_putchar_test:
            stp     x30, x19, [sp, #-16]!           // 16-byte Folded Spill  // save return address and a call-preserved reg
            mov     x19, x0                         // copy str to a call-preserved reg
    
            ldrb    w0, [x0]           // my_putchar(str[0]);
            bl      call_cout
            ldrb    w0, [x19, #1]
            bl      call_cout
            ldrb    w0, [x19, #2]
            bl      call_cout          // my_putchar(str[2]);
    
            mov     w0, #97
            bl      call_cout          // my_putchar(strconst[0])
            mov     w0, #98
            bl      call_cout
            ldp     x30, x19, [sp], #16             // 16-byte Folded Reload
            b       call_line_feed_out  // my_putchar(strconst[2]) tailcall
    

    因此,对于未知的运行时变量输入,我们没有运行时分支,对于字符串文字中的常量换行符,我们仍然可以调度到特殊情况函数。

    GCC对它的编译大致相同。

    推荐文章