__umoddi3()的问题

在编译内核时有人遇到下面这个问题:

kernel/built-in.o: In function `getnstimeofday':
(.text+0xb6ae): undefined reference to `__umoddi3'
kernel/built-in.o: In function `getnstimeofday':
(.text+0xb6ce): undefined reference to `__udivdi3'

这个问题可以在用户空间重现,不过不是很容易,我实验了一下,在i386上,并不是所有的64位整数操作都会被转化成调用__umoddi3,gcc bugzilla上有演示程序,如下:

[c]

define NSEC_PER_SEC 1000000000UL

int rmg(void);

int main(void)
{
/ int sec; /
return rmg();
}

int rmg(void)
{
static unsigned long long nsec = 0;
static int sec = 0;
while (sec = NSEC_PER_SEC, 0)) {
nsec -= NSEC_PER_SEC;
++sec;
}
}
return sec;
}
[/c]

这样编译它:% gcc -nostdlib -O2 -o umoddi3 umoddi3.c,就会得到:

/tmp/ccycM684.o: In function `rmg':
umoddi3.c:(.text+0x87): undefined reference to `__udivdi3'
collect2: ld returned 1 exit status

问题重现了。这里的问题是,对于nsec来说,内层的循环其实等价于求模运算,gcc在优化时发现了这一点,而且硬件本身也不支持对64位整数直接进行算术运算,所以gcc会把这一步优化成调用内部函数udivdi3()和umoddi3(),这两个函数在libgcc中(见gcc源代码gcc/libgcc2.c),libgcc默认和libc一样是要被加载的,但如果我们加了-nostdlib(Linux内核是更好的例子),这个问题就会出现了。

知道原因了,怎么解决?网上有两种方法,一种是像这个补丁那样,在循环中插入下面这条内联汇编:


asm(“” : “+r”(ns));

这句是告诉gcc把ns这个变量放到寄存器中,并且既有读操作也有写操作,所以后面再用它时必须重新读取,这样就消除了上面的优化。

另一种解决方法是添加新的编译选项:-fno-tree-scev-cprop,这个选项似乎没有文档,至少我没找到。说说它的大体意思。scev应该是SCalar EVolutions,什么意思不知道。:( cprop应该是Copy PROPagation,这个应该很容易理解,就是赋值的传播,比如:

i = 10;
a = i;
b = i;

其实就是:

a = 10;
b = 10;

可见,编译优化是门大学问,写个编译器丝毫不比写个内核容易。:-P