记录构建test-suite时遇到的一个问题(开启PGO选项)

这几天在学着用test-suite来跑benchmark,主要是为了之后的研究课题做准备。test-suite的基本构建和使用没问题了,但是在准备构建带有PGO(Profile Guided Optimization)的版本时,遇到了点问题,折腾了两天终于解决了。

一切要从参考test-suite的官网构建说起,首先是普通的构建

1
2
3
4
5
% mkdir test-suite-build
% cd test-suite-build
% cmake -DCMAKE_C_COMPILER=<path to llvm build>/bin/clang \
-C../test-suite/cmake/caches/O3.cmake \
../test-suite

构建没问题,跑测试没问题,跑benchmark没问题;好,很有精神!

之后准备构建用来跑PGO的test-suite,依然是参考官网的选项

1
2
3
4
5
6
# Profile generation run:
% cmake -DTEST_SUITE_PROFILE_GENERATE=ON \
-DTEST_SUITE_RUN_TYPE=train \
../test-suite
% make
% llvm-lit .

构建,有问题!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[ 83%] Building C object SingleSource/Regression/C/gcc-c-torture/execute/ieee/CMakeFiles/GCC-C-execute-ieee-fp-cmp-8e.dir/fp-cmp-8e.c.o
[ 83%] Building C object SingleSource/Regression/C/gcc-c-torture/execute/ieee/CMakeFiles/GCC-C-execute-ieee-fp-cmp-3.dir/fp-cmp-3.c.o
CMakeFiles/GCC-C-execute-ieee-compare-fp-3.dir/compare-fp-3.c.o: In function `test2':
compare-fp-3.c:(.text+0x4d): undefined reference to `link_error0'
CMakeFiles/GCC-C-execute-ieee-compare-fp-3.dir/compare-fp-3.c.o: In function `test3':
compare-fp-3.c:(.text+0x8d): undefined reference to `link_error0'
CMakeFiles/GCC-C-execute-ieee-compare-fp-3.dir/compare-fp-3.c.o: In function `test5':
compare-fp-3.c:(.text+0x113): undefined reference to `link_error1'
CMakeFiles/GCC-C-execute-ieee-compare-fp-3.dir/compare-fp-3.c.o: In function `test6':
compare-fp-3.c:(.text+0x163): undefined reference to `link_error1'
CMakeFiles/GCC-C-execute-ieee-compare-fp-3.dir/compare-fp-3.c.o: In function `all_tests':
compare-fp-3.c:(.text+0x1f0): undefined reference to `link_error0'
compare-fp-3.c:(.text+0x223): undefined reference to `link_error0'
compare-fp-3.c:(.text+0x2f6): undefined reference to `link_error1'
compare-fp-3.c:(.text+0x344): undefined reference to `link_error1'
[ 83%] Linking C executable GCC-C-execute-ieee-fp-cmp-1
clang-12: error: linker command failed with exit code 1 (use -v to see invocation)
[ 83%] Building C object SingleSource/Regression/C/gcc-c-torture/execute/ieee/CMakeFiles/GCC-C-execute-ieee-fp-cmp-8l.dir/fp-cmp-8l.c.o

我这一个萌新看到这么一个巨型项目报错,很方啊。先是去网上搜,无果;群里问,无果;自己折腾了好久,最后的结论只是-DTEST_SUITE_PROFILE_GENERATE=ON这个选项会导致error。无奈睡前斗胆往llvm-dev邮件列表里发了一封求助邮件,睡醒看到两位大佬的回信,受到启发的我把注意力从error本身转移到报错的文件上,即compare-fp-3.c。文件的逻辑大体上是,测试编译器是否会在开启编译优化的情况下,将无效分支给消除掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
extern void link_error0 ();
extern void link_error1 ();

void
test1 (float x, float y)
{
if ((x==y) && (x!=y))
link_error0();
}
...
#ifndef __OPTIMIZE__
void link_error0() {}
void link_error1() {}
#endif /* ! __OPTIMIZE__ */

看来是实际优化(无用分支是否被消除掉)和优化选项是否开启(是否有__OPTIMIZE__宏定义)之间的冲突。虽然知道了为什么这个文件报错,但是还是不知道是哪里导致的冲突。

然后又是走投无路,只能追着-DTEST_SUITE_PROFILE_GENERATE=ON编译选项去啃CMAKE规则文件,最后定位到的地方是test-suite/CMakeLists.txt:137

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if(TEST_SUITE_PROFILE_GENERATE)
find_program(TEST_SUITE_LLVM_PROFDATA NAMES "llvm-profdata"
HINTS ${CMAKE_C_COMPILER_DIRECTORY})
mark_as_advanced(TEST_SUITE_LLVM_PROFDATA)
if(TEST_SUITE_LLVM_PROFDATA STREQUAL "TEST_SUITE_LLVM_PROFDATA-NOTFOUND")
message(FATAL_ERROR "llvm-profdata not found.
Make sure it is in your path or set TEST_SUITE_PROFILE_GENERATE to OFF")
endif()

set(TEST_SUITE_PROFILE_GENERATE "True")

set(profile_instrumentation_flags -fprofile-instr-generate)
if(TEST_SUITE_USE_IR_PGO)
set(profile_instrumentation_flags -fprofile-generate)
endif()

list(APPEND CFLAGS ${profile_instrumentation_flags})
list(APPEND CXXFLAGS ${profile_instrumentation_flags})
list(APPEND LDFLAGS ${profile_instrumentation_flags})
else()
set(TEST_SUITE_PROFILE_GENERATE "False")
endif()

值得注意的是if分支最后的几行

1
2
3
4
5
6
7
8
set(profile_instrumentation_flags -fprofile-instr-generate)
if(TEST_SUITE_USE_IR_PGO)
set(profile_instrumentation_flags -fprofile-generate)
endif()

list(APPEND CFLAGS ${profile_instrumentation_flags})
list(APPEND CXXFLAGS ${profile_instrumentation_flags})
list(APPEND LDFLAGS ${profile_instrumentation_flags})

看来在打开TEST_SUITE_PROFILE_GENERATE选项时,会追加一个FLAG给编译器,接下来我继续谷歌这个FLAG-fprofile-instr-generate,发现时属于clang编译器的,之后我本来想在代码里找到它跟__OPTIMIZE__的联系,无果;又是山穷水尽的时候,这时候突然想到目前已知的几个条件,为何不直接测试clang中能不能触发这个冲突呢。然后就有了最关键的实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# in ~ [21:19:43] C:1
$ clang -fprofile-instr-generate compare-fp-3.c

# in ~ [21:19:51]
$ clang -O3 -fprofile-instr-generate compare-fp-3.c
/tmp/compare-fp-3-227c03.o: In function `test2':
compare-fp-3.c:(.text+0x4d): undefined reference to `link_error0'
/tmp/compare-fp-3-227c03.o: In function `test3':
compare-fp-3.c:(.text+0x8d): undefined reference to `link_error0'
/tmp/compare-fp-3-227c03.o: In function `test5':
compare-fp-3.c:(.text+0x113): undefined reference to `link_error1'
/tmp/compare-fp-3-227c03.o: In function `test6':
compare-fp-3.c:(.text+0x163): undefined reference to `link_error1'
/tmp/compare-fp-3-227c03.o: In function `all_tests':
compare-fp-3.c:(.text+0x1f0): undefined reference to `link_error0'
compare-fp-3.c:(.text+0x223): undefined reference to `link_error0'
compare-fp-3.c:(.text+0x2f6): undefined reference to `link_error1'
compare-fp-3.c:(.text+0x344): undefined reference to `link_error1'
clang-12: error: linker command failed with exit code 1 (use -v to see invocation)

# in ~ [21:20:34] C:1
$ clang -O0 -fprofile-instr-generate compare-fp-3.c

复现成功!这时突然注意到,最开始构建test-suite的时候,是开启了O3优化

1
2
3
4
5
% mkdir test-suite-build
% cd test-suite-build
% cmake -DCMAKE_C_COMPILER=<path to llvm build>/bin/clang \
-C../test-suite/cmake/caches/O3.cmake \
../test-suite

在分析出原因之后,我以为只要把上面的O3缓存改成O0缓存就没问题了,还是图样图森破,并不管用。在跟CMAKE纠缠中又度过了一天,在这个过程中也在测试用的.c文件中写上宏编译判断是否有__OPTIMIZE__宏定义,不出意外的有,但就是想不到这个幽灵一样的宏定义是从哪来的。

最后又把关注点移回到了CMAKE上,从原始缓存O0.cmake文件到生成的缓存CMakeCache.txt文件。最后通过在CMakeLists.txt中打印相关的变量,发现好像是O0.cmake这个原始缓存文件的信息没有成功写入,照着这个思路一谷歌,还真找到了答案

So CMake is ignoring your -C option here and tries to load your CMakeCache.txt as an actual variable cache file. And those files have a different formatting/syntax of NAME:TYPE=VALUE.

原来是当CMakeCache.txt存在时,原始缓存O0.cmake文件会被忽略。

最后的解决方法也瞬间明了了。

1
2
3
4
5
$ rm CMakeCache.txt

$ cmake -DCMAKE_C_COMPILER=<path to llvm build>/bin/clang \
-C../test-suite/cmake/caches/O0.cmake \
../test-suite

总结

总结一下,

  1. 这个error是由于O3优化和TEST_SUITE_PROFILE_GENERATE导致的冲突。仔细想一下,用来生成profile信息的程序当然不能用优化后的程序来profile呀。
  2. 对于CMAKE,当CMakeCache.txt存在时,原始缓存O0.cmake文件会被忽略。这点还真是让我挺吃惊的。

虽然又是一个历经险阻找到原因后直感自己蠢的场景,但是在这种巨型项目面前找到最后的拼图的感觉,还是异常爽快的。

后记

不知不觉,距离上一次更新博客已经三年半了,这次重启博客的动机主要有两个,一个是来自友人L的威胁

那我就要取关了

怎么能允许这种事情发生呢,好汉请留步,我什么都会做的,拜托了,我什么都会做的!

第二个是昨天(3.26)字节实习最后的HR面试,HR小姐姐也建议做一些知识输出,看在她那么好看的份上,我就勉(xin)为(xi)其(ruo)难(kuang)地采纳她的建议吧。