有没有办法在gcc中使用内联asm在编译时评估的表达式?

时间:2010-09-23 06:21:57

标签: gcc assembly compiler-errors inline-assembly

我有一些基本上需要在汇编语句中使用小表达式的代码,其中表达式非常简单,如i * 4,但GCC似乎没有意识到在编译时(尝试没有-O标志,和-O3)。对于第三种用法,“i”和“n”约束都不适用于以下代码段。

#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...

但这有其自身的问题,因为在进行字符串化时,parens仍然存在。我的意图是整个循环展开,但是-funroll-all-loops似乎没有在过程中尽早发生,导致i * 4成为常量。有任何想法吗?替代方案非常难看,但是如果有一种方法可以在宏观中实现自动化,那就更好了:

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

5 个答案:

答案 0 :(得分:0)

asm 块标记为 volatile 是否有任何特定原因?当 volatile 出现时,几乎不可能进行任何优化。

答案 1 :(得分:0)

不确定为什么你左移23 * 4 = 92,但是......

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

之类的东西
__builtin_choose_expr(__builtin_constant_p(c), SHIFT(h, l, c), slower_code_here);

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

它有时令人惊讶,它的想法是什么并且不是恒定的;前几天我一直在玩,它抱怨__builtin_choose_expr(sizeof(char[__builtin_choose_expr(..., 1, -1)]),...)

(我假设%2,%1,%0顺序是有意的;我会预期%0,%1,%2但文档含糊不清,我永远不会记得使用哪种asm语法。)

答案 2 :(得分:0)

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

答案 3 :(得分:0)

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

#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*可以“迭代”的宏仅限于一个用户提供的参数,因此你必须“嵌入”a或者在此示例中为b到实际的宏名称中。也许这可以通过另一个间接级别解决(将SHIFT(a)转换为SHIFT_a?)。没试过。

答案 4 :(得分:0)

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

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 */
  }
}

最重要的变化是:

  • 使用&#34; Jc&#34;对于(c)的约束。这允许gcc尽可能使用立即数,但如果必要则回退到rcx(即该值不适合&#34; J&#34;或者在编译时不知道该值)。
  • 使用%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使用immediates直到60,然后开始使用cl。

多田!