模数运算没有通过gcc优化?

时间:2013-07-01 15:32:33

标签: c optimization gcc assembly modulo

考虑这个添加常数的简单函数:

unsigned char f(unsigned char x) {
    return x + 5;
}

这将生成以下程序集(在gcc 4.7.2上使用-O3):

leal    5(%rdi), %eax
ret

既然无符号溢出在C中是明确定义的行为,人们会认为添加模运算本质上应该是一个nop:

unsigned char f(unsigned char x) {
    return (x + 5) % 256; // assume char is 8-bits, which is typical
}

但是生成的程序集有一个额外的指令:

leal    5(%rdi), %eax
movzbl  %al, %eax
ret

有人可以告诉我为什么会这样吗?虽然我对装配不是很熟悉。

(注意:这只是我用来理解GCC如何优化代码的玩具问题。)

1 个答案:

答案 0 :(得分:4)

对于“为什么生成的代码不同”的确切答案,您可能需要一位熟悉此gcc编译器详细信息的工程师。您可能希望通过以下几个示例进行更多实验:

unsigned char f1(unsigned char x) { return x + 5; }
unsigned char f2(unsigned char x) { return (x + 5) % 256; }
unsigned char f3(unsigned char x) { return (x + 5) % 256U; }
unsigned char f4(unsigned char x) { return (x + 5) & 0xFFU; }

gcc版本4.1.2适用于64位系统,我得到所有这些函数的相同代码,64位为32位代码。其中实际上包括movzbl。这可能是gcc编译f1中的错误(可能在来电方面得到纠正)。它实际上取决于调用约定:64位寄存器中的8位值是否应为零/符号扩展。我在2005年6月14日的 System V应用程序二进制接口,AMD64架构处理器补充的0.96版草案中找不到这个结论。gcc编译器4.1.2似乎采用了“更安全而不是抱歉”的理念,因为movzbl也出现在来电方面。根据我的经验,通常需要将这些值设置为零/符号扩展,除非有人对寄存器的某些部分进行操作,这是非常不寻常的。

有趣的是,我的家庭编译器gcc版本4.3.2在确实通过操作实现f2方面确实有所不同。所有其他人只需添加5,强烈建议呼叫者有责任执行零/符号扩展,这确实如此。但这是32位代码。

如果我在任何架构规范中找到超大寄存器中值/零/符号扩展的确定答案,那么我会告诉您。我碰巧也需要专业地了解这一点。

为你的gcc编译器辩护。您正在寻找小啤酒优化。普通代码不包含这样的模数,如果编译器下面的编译器将这种特殊模数减少到,那就太好了。如果是%256(vs %256U),则需要进行一些值范围分析,以确定是否足够,因为模数是在'signed'算术中完成的。很明显,我的编译器确实在某些时候得出结论就足够了,但显然已经太晚了,无法确定它是否被结果输入所包含,而在其他情况下它确定了。这就是我们编译工程师所说的“相序问题”。

更新寄存器中值的零/符号扩展名。

我现在已经放弃了这个任务,并且必须继续与一些同事继续,因为如果参数/函数结果预计为零/符​​号扩展,我还没有找到确凿的陈述。

我确实在上述ABI规范中找到了与此相关的内容。

  

布尔值存储在内存对象中时,存储为单字节对象,其值始终为0(假)或1(真)。当存储在整数寄存器中或作为参数传递到堆栈时,寄存器的所有8个字节都是重要的;任何非零值都被视为真。

所以布尔类型必须为零扩展。

  

对于可能调用使用varargs或stdargs的函数的调用(无原型调用或对声明中包含省略号(...)的函数的调用)%al(注释14)用作隐藏参数来指定使用的SSE寄存器的数量。 %al的内容不需要与寄存器的数量完全匹配,但必须是所使用的SSE寄存器数量的上限,并且在0-8的范围内。

     

注意14:请注意,%rax的其余部分未定义,仅定义了%al的内容。

因此,%al的这种特殊用法无需扩展。

考虑到布尔值必须为零扩展,可以得出结论,ABI的精神是其他子词类型也应该被扩展。采取更正式的立场可以说,没有任何陈述应该被解释为不需要零/符号扩展。总而言之,并不令人满意。

在寄存器中值的零/符号扩展时更新2.

我和一位同事讨论了这个问题。 2012版0.99的ABI的最新版本已经完全根据布尔值的参数传递进行了更改,因为这些只是零扩展到8位。这表明这已被修改为与传递其他子词类型一致,因为所有零/符号扩展。 AMD64架构还支持64位寄存器中一半的子字寄存器,并可对这些子字寄存器进行操作。这可能是不以零/符号扩展方式传递参数的动机。