【问题标题】:Is there a way to use expressions evaluated at compile-time with inline asm in gcc?有没有办法在编译时使用 gcc 中的内联 asm 评估表达式?
【发布时间】:2010-09-23 06:21:56
【问题描述】:

我有一些代码基本上需要在汇编语句中使用一个小表达式,其中表达式像 i*4 一样微不足道,但 GCC 在编译时似乎没有意识到这一点(尝试没有 -O 标志,和-O3)。对于第三种用法,“i”和“n”约束在以下 sn-p 中均不起作用。

#include <stdint.h>
#include <stdlib.h>

#define SHIFT(h, l, c) __asm__ volatile ( \
    "shld %2, %1, %0\n\t" \
    "sal %2, %1\n\t" \
    : "+r"(h), "+r"(l) : "i"(c))

void main(void) {
  uint64_t a, b;
  SHIFT(a, b, 1); /* 1 */
  SHIFT(a, b, 2*4); /* 2 */
  size_t i;
  for(i=0; i<24; i++) {
    SHIFT(a, b, (i*4)); /* 3 */
  }
}

给出这个错误:

temp.c:15: warning: asm operand 2 probably doesn’t match constraints
temp.c:15: error: impossible constraint in ‘asm’

我也试过

"shld $" #c ", %1...

但这有其自身的问题,因为字符串化时括号仍然存在。我的意图是整个循环展开,但 -funroll-all-loops 似乎在过程中发生得不够早,导致 i*4 成为常数。有任何想法吗?替代方案非常丑陋,但如果有一种方法可以在宏中自动执行此操作,那总比没有好:

SHIFT(a, b, 1);
SHIFT(a, b, 2);
...
SHIFT(a, b, 24);

【问题讨论】:

    标签: gcc assembly compiler-errors inline-assembly


    【解决方案1】:

    是否有任何具体原因将 asm 块标记为 volatile?几乎不可能在 volatile 存在时进行任何优化。

    【讨论】:

    • 我这样做是为了防止它被优化掉,因为在示例中没有使用返回值。我尝试删除 volatile、添加初始值和最后的 printf,但结果相同(在不可能的约束上编译错误)。我的印象是阅读gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html 仍然可以移动易失性 asm,您是否有描述它阻碍的优化(除了删除未使用的代码)的链接?
    • 好的,那么问题可能与以下主题有关:stackoverflow.com/questions/1478513/…
    【解决方案2】:

    不知道为什么要向左移动 23*4=92,但是...

    可能会有。您可以使用 __builtin_constant_p() 和 __builtin_choose_expr() 来选择要编译的表达式;像

    __builtin_choose_expr(__builtin_constant_p(c), SHIFT(h, l, c), slower_code_here);
    

    如果它选择了slower_code_here,那么它“不能”确定c 是常数。如果它抱怨“不可能的约束”,那么它知道它是恒定的,但由于某种原因无法将它变成立即数。

    有时它的想法是不变的,这令人惊讶;前几天我在玩,它抱怨__builtin_choose_expr(sizeof(char[__builtin_choose_expr(..., 1, -1)]),...)之类的东西。

    (我假设 %2,%1,%0 的顺序是故意的;我原以为是 %0,%1,%2 但文档含糊不清,我永远记不起正在使用哪种 asm 语法.)

    【讨论】:

      【解决方案3】:

      您假设编译器将展开您的循环并每次替换 i * 4 的值......这有点假设。 * 4 看起来您想要某种寻址修改,为什么不传入 i 并编写指令来执行您的 * 4仔细查看 GCC 处理的约束,并确保您的指令确实采用了您的约束可能引发的所有组合。

      【讨论】:

        【解决方案4】:

        您的“丑陋”方式可以使用 Boost 预处理器 库(实际上是一组 cpp 宏,也是 Boost 中唯一可以与普通 C 一起使用的部分)来实现: p>

        #include <boost/preprocessor/repetition/repeat.hpp>
        
        #define SHIFT_a(z, CNT, b) __asm__ volatile ( \
            "shld %2, %1, %0\n\t" \
            "sal %2, %1\n\t" \
            : "+r"(a), "+r"(b)
            : "i"(CNT * 4)
            : "cc");
        
        void main(void) {
            uint64_t a, b;
        
        // whatever ...
        
            BOOST_PP_REPEAT_FROM_TO(1, 25, SHIFT_a, b)
        }
        

        其中的“丑陋”位是 BOOST_PP_REPEAT* 可以“迭代”的宏仅限于一个用户提供的参数,因此您必须“嵌入”ab在本例中输入实际的宏名称。也许这可以通过另一个间接级别来解决(将 SHIFT(a) 转换为 SHIFT_a ?)。没试过。

        【讨论】:

          【解决方案5】:

          我怀疑您是否仍然对这个问题的反馈感兴趣,但由于您从未接受任何其他答案...

          OP 代码存在一些问题,但经过一些清理,您会得到:

          #include <stdint.h>
          
          #define SHIFT(h, l, c) __asm__ volatile ( \
              "shld %b2, %1, %0\n\t" \
              "sal %b2, %1\n\t" \
              : "+r"(h), "+r"(l) : "Jc"(c))
          
          int main(void) {
            uint64_t a, b;
            a = b = 0;
            SHIFT(a, b, 1); /* 1 */
            SHIFT(a, b, 2*4); /* 2 */
            size_t i;
            for(i=0; i<16; i++) {
              SHIFT(a, b, (i*4)); /* 3 */
            }
          }
          

          最显着的变化是:

          • 使用“Jc”作为 (c) 的约束。这允许 gcc 在可能的情况下使用立即数,但在必要时回退到 rcx(即该值不适合“J”或该值在编译时未知)。
          • 使用 %b2 而不仅仅是 %2。这给了我们 cl 而不是这些指令所需要的 rcx。
          • 将循环大小更改为 16。sal 和 shld 仅允许在 x64 上进行 0-63 的移位(在 x86 上为 0-31)。

          -O2 -m64 -funroll-all-loops -S编译,我们看到:

          /APP
           # 12 "shl.cpp" 1
                  shld $1, %rdx, %rax
                  sal $1, %rdx
          
           # 0 "" 2
           # 13 "shl.cpp" 1
                  shld $8, %rdx, %rax
                  sal $8, %rdx
          
           # 0 "" 2
           # 16 "shl.cpp" 1
                  shld $0, %rdx, %rax
                  sal $0, %rdx
          
           # 0 "" 2
           # 16 "shl.cpp" 1
                  shld $4, %rdx, %rax
                  sal $4, %rdx
          ...
          
           # 0 "" 2
           # 16 "shl.cpp" 1
                  shld $60, %rdx, %rax
                  sal $60, %rdx
          
           # 0 "" 2
          /NO_APP
          

          有趣的是,如果你使用 i*6 而不是 i*4,你会看到 gcc 使用立即数直到 60,然后开始使用 cl。

          多田!

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-08-07
            • 2016-11-11
            • 2014-06-16
            • 1970-01-01
            • 2010-10-31
            • 1970-01-01
            • 2012-12-26
            相关资源
            最近更新 更多