From the very beginning, the PVS-Studio C ++ analyzer for Windows (then still Viva64 version 1.00 in 2006) was assembled with the MSVC compiler. With the release of new C ++ releases, the analyzer core learned to work on Linux and macOS, and the project structure was switched to use CMake. But on Windows, the build was still done using the MSVC compiler. On April 29, 2019, the Visual Studio developers announced the inclusion of a set of LLVM utilities and the Clang compiler in their development environment. And now we finally got our hands on to try it in action.
As a benchmark, we will use our utility for regression testing of the analyzer called SelfTester. The essence of her work is to analyze a set of different projects and compare the analysis results with the reference ones. For example, if after some edits in the analyzer kernel false warnings appear or the correct ones disappear, then there is a regression that needs to be corrected. More details about SelfTester can be found in the article “Best the enemy of the good“.
The test base includes projects that are quite diverse in terms of code size. As a rule, if the working computer or test server is not loaded, then the testing time by SelfTester on the same kernel version varies within the margin of error. If the analyzer’s performance is not saved, this will significantly affect the total testing time.
After the assembly of the C ++ kernel switched to Clang, SelfTester began to run 11 minutes faster.
The 13% performance gain is quite noticeable considering that it is enough to simply change the compiler, right?
There are also disadvantages, but insignificant. The distribution kit build slowed down by 8 minutes, and the size of the executable file grew by 1.6 MB (of which ~ 500 KB due to static linking of the runtime).
Apparently, performance is achieved by a longer LTO stage (most of the assembly is occupied by linking) and more aggressive loop unwinding and inline functions.
Next, I would like to share the pitfalls that arose during the transition.
Build generation for Clang
CMake scripts allow us to build our code with all mainstream compilers for the required operating systems.
First of all, you need to install the Clang compiler components via the Visual Studio Installer.
Clang-cl is a so-called “driver” that allows clang to be used with parameters from cl.exe. Thus, it should interact transparently with MSBuild, much like a native compiler.
You can also use the official assemblies from the LLVM project, which can be found on their repository Github… However, they require an additional plugin to be installed so that Visual Studio can find compilers. The name of the toolset will be llvm, but not clangclas shown in the examples below.
We specify the toolchain in the solution generation command for Visual Studio:
cmake -G "Visual Studio 16 2019" -Tclangcl <src>
Or use the GUI:
We open the resulting project, collect. And, of course, we get a bunch of errors.
Fixing the assembly
Although clang-cl outwardly behaves like CL, under the hood it is a completely different compiler, with its own gags.
We try not to ignore compiler warnings, so we use the / W4 and / WX flags. However, Clang may generate additional warnings that are currently interfering with builds. For now, turn them off:
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
A little better.
The GCC and Clang compilers have built-in support for the type int128, unlike MSVC under Windows. Therefore, at one time, a wrapper was written with the implementation Int128 for Windows (in assembler inserts and wrapped with ifdefs, in the best traditions of C / C ++). Let’s correct the definitions for the preprocessor by replacing:
if (MSVC AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
Usually the library with builtins is passed to the linker (lld) by the compiler driver, be it clang.exe or clang-cl.exe. But in this case, the linker is fueled by MSBuild directly, which does not know to use it. Accordingly, the driver cannot pass flags to the linker in any way, so you have to figure it out yourself.
if (CMAKE_GENERATOR MATCHES "Visual Studio")
Hooray! The assembly is working. However, further, when running the tests, a bunch of segmentation errors awaited us:
At the same time, the debugger shows some strange value in IntegerInterval, but in fact the problem is a little further:
The previously mentioned type is actively used in different structures for the Dataflow mechanism. Int128, and SIMD instructions are used to work with it. And the drop is caused by an unaligned address:
The MOVAPS instruction moves a set of floating point numbers from memory to registers for SIMD operations. In this case, the address must be aligned, at its end there must be 0, but it turned out to be 8. We’ll have to help the compiler by setting the correct alignment:
class alignas(16) Int128
The last problem came out because of Docker containers:
Building for MSVC was always done with static linking of the runtime, and for experiments with Clang, the runtime was switched to dynamic. It turned out that Microsoft Visual C ++ Redistributable is not installed by default in Windows images. We decided to return the static linking so that users would not have the same troubles.
Despite the fact that we had to tinker a little with the preparation of the project, we were satisfied with the increase in the analyzer’s performance by more than 10%.
Subsequent releases of PVS-Studio on Windows will be built using the Clang compiler.
If you want to share this article with an English-speaking audience, please use the translation link: Alexey Govorov, Sergey Larin. PVS-Studio Team: Switching to Clang Improved PVS-Studio C ++ Analyzer’s Performance.