ARM 的 ndk 和 clang++ 中的编译器错误?
Compiler error in ndk and clang++ for ARM?
请考虑以下代码:
float test(int len, int* tab)
{
for(int i = 0; i<len; i++)
tab[i] = i;
}
显然 return 不见了。对于 ARM 处理器的 clang 和 ndk 编译器的这种情况,会生成一个无限循环。反汇编后很明显编译器生成常规分支指令而不是条件分支。
mov r0, #0
.LBB0_1:
str r0, [r1, r0, lsl #2]
add r0, r0, #1
b .LBB0_1
有错误的例子可以在这里找到:https://godbolt.org/z/YDSFw-
请注意,c++ 规范声明缺少 return 被视为未定义行为,但它仅指代 returned 值。不影响前面的说明。
我是不是漏掉了什么?有什么想法吗?
不,你不能用未定义的行为那样推理。
编译器可以自由使用未定义的行为和围绕它的假设进行优化。编译器可以自由假设您的代码不会包含未定义的行为。
在这种情况下,编译器可以假定不会到达具有未定义行为的代码。由于函数的末尾包含未定义的行为,编译器断定实际上永远不会到达函数的末尾,因此可以优化循环。
如果您删除 -Oz
并将 -emit-llvm
添加到编译器资源管理器命令,您将看到 LLVM IR clang 在不进行优化时最初生成的内容:
https://godbolt.org/z/-dbeNj
define dso_local float @_Z4testiPi(i32 %0, i32* %1) #0 {
%3 = alloca i32, align 4
%4 = alloca i32*, align 4
%5 = alloca i32, align 4
store i32 %0, i32* %3, align 4
store i32* %1, i32** %4, align 4
store i32 0, i32* %5, align 4
br label %6
6: ; preds = %15, %2
%7 = load i32, i32* %5, align 4
%8 = load i32, i32* %3, align 4
%9 = icmp slt i32 %7, %8
br i1 %9, label %10, label %18
10: ; preds = %6
%11 = load i32, i32* %5, align 4
%12 = load i32*, i32** %4, align 4
%13 = load i32, i32* %5, align 4
%14 = getelementptr inbounds i32, i32* %12, i32 %13
store i32 %11, i32* %14, align 4
br label %15
15: ; preds = %10
%16 = load i32, i32* %5, align 4
%17 = add nsw i32 %16, 1
store i32 %17, i32* %5, align 4
br label %6
18: ; preds = %6
call void @llvm.trap()
unreachable
}
循环结束,标签 18,包含 unreachable
。这可用于进一步优化,在循环开始时摆脱分支和比较。
编辑:
John Regehr 发表了一篇关于如何推理 C 和 C++ 中未定义行为的文章 excellent blog post。有点长,但值得一读。
请考虑以下代码:
float test(int len, int* tab)
{
for(int i = 0; i<len; i++)
tab[i] = i;
}
显然 return 不见了。对于 ARM 处理器的 clang 和 ndk 编译器的这种情况,会生成一个无限循环。反汇编后很明显编译器生成常规分支指令而不是条件分支。
mov r0, #0
.LBB0_1:
str r0, [r1, r0, lsl #2]
add r0, r0, #1
b .LBB0_1
有错误的例子可以在这里找到:https://godbolt.org/z/YDSFw-
请注意,c++ 规范声明缺少 return 被视为未定义行为,但它仅指代 returned 值。不影响前面的说明。
我是不是漏掉了什么?有什么想法吗?
不,你不能用未定义的行为那样推理。
编译器可以自由使用未定义的行为和围绕它的假设进行优化。编译器可以自由假设您的代码不会包含未定义的行为。
在这种情况下,编译器可以假定不会到达具有未定义行为的代码。由于函数的末尾包含未定义的行为,编译器断定实际上永远不会到达函数的末尾,因此可以优化循环。
如果您删除 -Oz
并将 -emit-llvm
添加到编译器资源管理器命令,您将看到 LLVM IR clang 在不进行优化时最初生成的内容:
https://godbolt.org/z/-dbeNj
define dso_local float @_Z4testiPi(i32 %0, i32* %1) #0 {
%3 = alloca i32, align 4
%4 = alloca i32*, align 4
%5 = alloca i32, align 4
store i32 %0, i32* %3, align 4
store i32* %1, i32** %4, align 4
store i32 0, i32* %5, align 4
br label %6
6: ; preds = %15, %2
%7 = load i32, i32* %5, align 4
%8 = load i32, i32* %3, align 4
%9 = icmp slt i32 %7, %8
br i1 %9, label %10, label %18
10: ; preds = %6
%11 = load i32, i32* %5, align 4
%12 = load i32*, i32** %4, align 4
%13 = load i32, i32* %5, align 4
%14 = getelementptr inbounds i32, i32* %12, i32 %13
store i32 %11, i32* %14, align 4
br label %15
15: ; preds = %10
%16 = load i32, i32* %5, align 4
%17 = add nsw i32 %16, 1
store i32 %17, i32* %5, align 4
br label %6
18: ; preds = %6
call void @llvm.trap()
unreachable
}
循环结束,标签 18,包含 unreachable
。这可用于进一步优化,在循环开始时摆脱分支和比较。
编辑: John Regehr 发表了一篇关于如何推理 C 和 C++ 中未定义行为的文章 excellent blog post。有点长,但值得一读。