C/C++将int转换为short和inline asm(ARM特定)

9
这不是一个简单的问题。
注意:我不需要使用纯汇编的意见或建议。我实际上需要完成我所说的事情:在将结果分配给short int时获取内联汇编,而无需此符号/零扩展操作码。

我正在处理一个滥用16位短整型的库,并对其进行优化。我需要添加一些带有内联汇编的优化函数。问题在于,在许多地方,函数的结果被分配给了short int。也就是说,编译器生成uxth或sxth arm操作码。

我的目标是避免这个问题,并确保不生成这个无用的操作码。 首先,我需要定义我的优化函数以返回short int。这样,如果它被分配给int或short int,就没有额外的操作码来转换结果。

问题在于,我不知道如何跳过编译器在我的函数内生成的int->short转换。
愚蠢的强制转换,例如:*(short*)(void*)&value 不起作用。编译器要么开始搞乱堆栈,使问题变得更加复杂,要么仍然使用同样的sxth来符号扩展结果。

我编译多个编译器,我已经能够解决armcc编译器的问题,但我无法在GCC(我使用4.4.3或4.6.3编译)中完成。在armcc中,我在内联汇编语句中使用short类型。即使我在gcc中使用short,编译器仍然认为需要符号扩展。

这是一个简单的代码片段,我无法让它在GCC中工作,请问如何让它工作?对于这个简单的示例,我将使用clz指令:

static __inline short CLZ(int n)
{
    short ret;
#ifdef __GNUC__
    __asm__("clz %0, %1" : "=r"(ret) : "r"(n));
#else
    __asm { clz ret, n; }
#endif
    return ret;
}

//test function
short test_clz(int n)
{
    return CLZ(n);
}

以下是使用armcc -c -O3编译后的预期结果:
test_clz:
    CLZ      r0,r0
    BX       lr

以下是GCC -c -O3给我的不可接受的结果:

test_clz:
    clz r0, r0
    sxth    r0, r0
    bx  lr

请注意,如果使用内部变量int ret;而不是short ret;重写CLZ,则armcc生成的结果与GCC相同。
获取gcc或armcc的汇编输出的快速方法:
gcc -O3 -c test.c -o test.o && objdump -d test.o > test.s
armcc -O3 --arm --asm -c test.c

1
为什么不跳过内联汇编,直接编写一个完整的汇编函数来编写优化位呢?你的问题似乎来自于C函数和内联汇编的混合。但是,为什么要编写一个只包含一堆内联汇编的C函数呢? - TJD
我重写了一些真正需要完全用asm编写的函数。要做好这项工作,我可能需要检查整个代码并使用int而不是shorts,但仅此任务就可能花费我数天时间来更新和测试所需的代码量。 - Pavel P
2个回答

6
编译器会发生变化。特别是gcc,今天你可能想到的技巧明天就行不通了,或者昨天就行不通了。而且这些技巧在不同的编译器(armcc、clang等)之间也不一定能够始终奏效。
1)删除shorts并将其替换为int,这是一个选项,也是最不痛苦的解决方案。
2)如果您需要特定的汇编语言,请编写特定的汇编语言,不要折腾。这也是一个选项。
虽然很有可能编写出的代码比其他代码更容易编译,但您并不能总是得到您想要的确切代码序列,尤其是不能始终如一地得到。即使是编写自己的汇编语言解决方案,您也会在长期内受到损害。您实际上正在寻找的解决方案是浏览代码并用int替换shorts,这将产生与保留shorts相比始终更容易编译的代码。总体而言,这将花费更少的时间,并且不必随着编译器的更改而每隔几个月重写代码。
要完全控制这个问题,可以编译为asm或反汇编并删除有问题的指令,将函数留在asm中。这是完成任务的快速简便方法,将为您消除这种开销,只是留下了一些不太易于维护的东西。实际上,由于您已经让armcc按照您的要求编译为asm,然后修补gnu汇编器的愚蠢习惯,使用它作为一个解决方案(在arm ads时代,至少可以编写汇编语言,可以在arm工具和gnu中都汇编,我失去了工具的访问权限之前没有太多rvct时间)。
有许多方法可以使您提供的确切示例给出您想要的确切结果,但我严重怀疑这就是您想要的,您可能只需要编写两行汇编代码就可以了。我猜测您正在尝试在函数中内联调用某些内容(大于CLZ),同时仍然将其称为shorts,而将其称为int将在没有内联汇编的情况下给您想要的结果。(我仍然看不出在任何地方内联汇编短时间比更改变量声明、输入更少,读取和测试相同的代码需要更短的时间)。
所以这就是现实:
1)接受shorts及其副作用。
2)将它们更改为int。
花费几天、几周或几个月来做某事并不是什么大问题。大多数情况下,花费的时间是为了避免做某事。然后你还得去做,所以现在你需要2倍的天数、2倍的周数、2倍的月数……无论采取什么解决方案,您都必须或应该进行测试,因为您正在更改代码,因此这不是这个决策中的变量因素。使用内联汇编攻击编译器是最高风险的,如果测试确实是时间方程式中的变量因素,则应该进行最多的测试。需要几个gcc版本,并且每6个月重新测试。
通常情况下,当ABI变化时,汇编语言解决方案可能需要重新测试约10年,而仅修复C可能需要20年,例如从64位到128位。但是32位到64位的转换仍在进行中,我们尚未开始ARM 32位到64位转换/混合(不会放弃所有64位的32位ARM处理器,两者都将保留)。后端处理程序将在一段时间内变得混乱,现在不要与它们玩游戏。制作干净、可移植的C代码,其中您不依赖于代码中的int大小(假设/要求最少32位,但确保其为64位),这是您最便宜的解决方案。

嗨dwelch,感谢您提供如此长而详尽的回复。我清楚地理解了您所有的观点,并且在我真正需要它的地方已经做到了这一点。此时,我想要的只是让GCC在我知道不需要的地方不生成SXTH(基本上,我不需要知道选项,可能我已经知道所有选项)。我想要的只是用我的编译器获得正确的行为,然后我就完成了(即使将来的编译器再次添加SXTH,也没关系)。 - Pavel P
简而言之,我期望有某种gcc特定的“hack”来转换类型或者其他内联汇编修饰符,也许可以得到那种行为。我尝试了不同的转换和修饰符,但没有帮助。 - Pavel P

1
如果您追求的是速度而不是代码大小,您可以尝试这个:
static __inline short CLZ(int n)
{
    short ret;
#ifdef __GNUC__
    __asm__("clz %0, %1\n"
            "bx lr"
            : "=r"(ret) : "r"(n));
#else
    __asm { clz ret, n; }
#endif
    return ret;
}

更新以添加:在我看来,gcc编译器在这里做了正确的事情。在C(而不是C++)中,没有返回short的函数--它总是会自动转换为int。所以你别无选择,只能愚弄编译器。如果您将文件名更改为test.cpp,会发生什么?


Tony,我会尝试,但不确定内联 bx lr 是否会对优化器产生负面影响,或者是否可能。显然,如果它仅仅是让示例运行起来,并不是一个解决方案:CLZ的这个简短版本在很多地方都被使用,而在使用SXTH的地方不应该生成。虽然它使用相同的32位寄存器,但确实有返回short的情况。但是gcc认为需要进行符号扩展,而我知道这是不必要的。基本上,SXTH函数确保顶部16位为零或一,但我知道该函数的输入已经处于正确的布局中。 - Pavel P
此外,我实际上使用C++编译来获得一些简单的功能,如函数重载等。但在这个例子中,无论是C还是C++编译都没有区别。 - Pavel P
我尝试了额外的bx lr,似乎gcc没有进行操作码级别的优化:在bx lr之后,它再次添加了相同的一对:SXTH + 另一个bx lr。简而言之,如果test_clz有这个主体:CLZ(CLZ(a)),那么结果会很糟糕:当内联时,GCC仍然会添加bx lr,这会无意中跳出调用函数。也许这可以在llvm / clang中使用,其中它执行一些操作码级别的优化,并且可能检测到sxth不是必要的或者从内联汇编本身返回。 - Pavel P
@Pavel:在我的“bx lr”之后,任何后续的指令都不会被执行,因为函数已经返回。那么这为什么不能解决你的问题呢?至于对优化器产生负面影响,内联GCC汇编根本不分析内联指令,所以你可以忘记它。但我很惊讶这种情况在C++中依然存在,你确定吗? - TonyK
当然它们是内联的。即使在这个例子中,CLZ也被内联到test_clz中。 - Pavel P
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接