
Recently, our team completed the migration to a 64-bit platform of a content large project (9 million lines of code, 300Mb sources). The project took a year and a half. Although, due to NDA, we cannot give the name of the project, we hope that our experience will be useful for other developers.
About the authors
Many people know us as authors of the
PVS-Studio static code analyzer. This is really our main activity. However, apart from this, we are still participating in third-party projects as a team of experts. We call it "sale of expertise." We recently published a
report on the work on the Unreal Engine 4 code . Today is the time of the next report on the work done as part of the sale of our expertise.
')
“Yeah, it means that they are doing quite badly with PVS-Studio!”, The reader, who follows our activities, may exclaim. We hurry to upset the audience for sensations. Participation in such projects is very important for our team, but for a completely different reason. Thus, we can use our code analyzer more actively in real life than simply during its development. The real use of the analyzer in commercial projects, on which dozens or even hundreds of programmers work, gives a great experience to the PVS-Studio team. We see how our tool is applied, what difficulties arise and what needs to be changed or at least just improved in our product.
Therefore, we plan to continue to participate in such projects as part of the sale of our expertise. Write if you have a project suitable for us. In the meantime, we are pleased to submit a report on the migration of code to the 64-bit platform.
Introduction, or what is the problem? Project scope and team size
At first glance, everything is clear with the topic of code migration to the x64 platform. The time-tested article "
A collection of examples of 64-bit errors in real programs " was written by us in 2010. Our course "
Lessons on development of 64-bit C / C ++ applications " - in 2012. It would seem, read, do as it is written, and everything will be fine. Why did the customer need to contact a third-party organization (us), and why even we spent a year and a half on the project? After all, if we are doing an analysis of 64-bit problems within PVS-Studio, then it would seem that we should understand the topic? Of course we understand, and this was the main reason that the customer turned to us. But why did the customer even have the idea to contact someone about 64-bit migration?
Let's first describe the project and the customer. Since the NDA forbids speaking directly, we present only quantitative characteristics. The project we have been working on is about 20 years. Currently, several dozens of developers are working on it daily. Customers are large companies, sales are rare, as the product is very niche and highly specialized.
Well, the most important thing is the size. 9 million lines of code, 300Mb of source code, a thousand projects in the solution (.sln) is VERY much. Platform - Windows only. But even with such a project, 64-bit migration seems to be understandable. In order to translate such a project to x64 you need only:
- stop the development for several months;
- quickly replace data types with 64-bit ones;
- check that everything works correctly after replacement;
- You can go back to the development.
Why is the first item set to “completely stop development”? Yes, because for 64-bit migration, of course, you need to replace some data types with 64-bit ones. If you create a separate branch in a project of this size and make all the necessary edits there, then it will be impossible to merge the code (perform merge)! Do not forget about the scope of the project and dozens of programmers who write new code every day.
Due to business constraints, the customer could not stop the development process. His clients constantly need new releases, bug fixes, special features, etc. To stop development in such conditions means to stop the business. Therefore, the customer began to look for a team that can perform the migration without stopping the development process. We have become such a team, since our competence in 64-bit development is confirmed by the PVS-Studio code analyzer and articles on this topic.
We completed the migration in a year and a half. From our side, two people took part in the project for the first half year, then another four years ago. Why so long? For the first six months, two people were engaged in setting up the infrastructure, getting to know the project, and checking specific migration methods. Then, six months later, when the task became more specific, more people joined the project and already 4 people completed the migration in a year.
How to transfer a project to a 64-bit system?
The transfer of the project to the 64-bit platform by and large consists in the following two steps:
- Creating a 64-bit configuration, getting 64-bit versions of third-party libraries and building a project.
- Correction of code that leads to errors in the 64-bit version. This clause is almost completely reduced to the fact that you need to replace the 32-bit types with memsize types in the program code.
Recall that under the memsize-types understand the types of variable dimension. These types are 4 bytes on a 32-bit system and 8 bytes on a 64-bit one.
Porting a large and actively developing project should not interfere with the current development, so we have taken the following measures. First, we made all our edits in a separate branch, so as not to break the main assembly. When the next set of our changes was ready and tested, we merged our changes with the main branch. And secondly, we did not change hard 32-bit types to memsize-types. We introduced our types and made a replacement for them. This was done in order to avoid potential problems, such as, for example, calling another implementation of an overloaded function, and also to be able to quickly roll back our changes. Types were introduced approximately as follows:
#if defined(_M_IX86) typedef long MyLong; typedef unsigned long MyULong; #elif defined(_M_X64) typedef ptrdiff_t MyLong; typedef size_t MyULong; #else #error "Unsupported build platform" #endif
We would like to emphasize once again that we have changed types not for size_t / ptrdiff_t and the like, but for our own data types. This gave a great flexibility and the ability to easily track those places that have already been ported from those where “no man’s foot has gone before.”
Possible approaches to migration: their pros and cons, in which we made a mistake
The first idea of ​​porting the project was as follows: first, replace all 32-bit types with memsize-types, except for those places where 32-bit types were to be left (for example, structures representing data formats, functions processing such structures), and then bring the project to a working condition. We decided to do so in order to immediately eliminate as many 64-bit errors as possible and do it in one pass, and then correct all the remaining warnings of the compiler and PVS-Studio. Although this method works for small projects, in our case it did not fit. First, type replacement took too much time and led to a lot of changes. And secondly, no matter how hard we tried to do it, we, nevertheless, ruled structures with data formats by mistake. As a result, when we finished working on the first part of the projects and started the application, we could not load the pre-installed interface templates, since they were binary.
So, the first plan suggested the following sequence of actions.
- Creating a 64-bit configuration.
- Compilation.
- Replacing most 32-bit types with 64-bit types (or rather, memsize-types).
- Linking with third-party libraries.
- Application launch.
- Edit the remaining compiler warnings.
- Edit the remaining 64-bit errors that will be detected by the PVS-Studio analyzer.
And this plan was declared unsuccessful. We completed the first five points, and all our changes to the source code had to be rolled back. We have wasted several months of work.
Now we decided to get the working 64-bit version of the application as soon as possible, and then fix the obvious 64-bit errors. Our plan now excluded mass type replacement and suggested editing only obvious 64-bit errors:
- Creating a 64-bit configuration.
- Compilation.
- Linking with third-party libraries.
- Application launch.
- Edit compiler warnings.
- Editing the most priority 64-bit errors that will be revealed by the PVS-Studio analyzer.
This time we received the first working version of the application much faster, including because third-party libraries were already assembled, and the interface templates loaded correctly. I must say that the application basically worked quite stably, which surprised us. We found only a few drops when testing for the first time.
Next we had to fix the compiler warnings and the 64-bit warnings of PVS-Studio to eliminate the found and potential crashes. Since the total number of 64-bit warnings of PVS-Studio was in the thousands, we decided to correct only the most basic ones: implicit conversions of memsize types to 32-bit types (V103, V107, V110), conversions of pointers to 32-bit types and vice versa ( V204, V205), suspicious chains of transformations (V220, V221), alignment of types in the parameters of virtual functions (V301) and replacement of obsolete functions with new versions (V303). A description of all these diagnostics can be found in the
documentation .
In other words, the task of this stage is to correct all 64-bit PVS-Studio messages of the first level only (level 1). This is the most important diagnostic. And to run a 64-bit application, all 64 L1 errors must be fixed.
Most of these edits come down to replacing 32-bit types with memsize-types, as in the first approach. But this time, unlike the first approach, these replacements were selective and iterative. This was due to the fact that the editing of the types of the parameters of the function pulled over for itself the editing of the types of local variables and the return value, which in turn led to the editing of the types of parameters of other functions. And so on until the process came together.
Another disadvantage of this approach in comparison with the first one is that we have thus corrected only the main 64-bit errors. For example, we did not rule the types of cycle counters. In most cases it was not necessary. And this does not lead to mistakes, but perhaps somewhere it had to be done, and we missed such places and did not find them with our approach. In other words, perhaps over time, something else will have to be corrected.
When porting the application, we also needed to get 64-bit versions of third-party libraries. In the case of open source libraries, we tried to build them from the same sources from which 32-bit versions were compiled. This was due to the fact that we wanted to save possible edits in the code of third-party libraries, if there were any, and also we needed to collect them in the same configuration as they were for the 32-bit version. For example, some libraries were built with the setting not to consider wchar_t as a built-in type or with unicode support disabled. In such cases, we had to tinker a bit with the build parameters before we could understand why our project could not link with them. Some libraries did not anticipate an assembly for the 64-bit version. And in this case, we had to either convert them ourselves, or download a newer version with the ability to build under a 64-bit platform. In the case of commercial libraries, we either asked to purchase a 64-bit version, or we were looking for a replacement for non-supported libraries, as is the case with xaudio.
We also needed to get rid of all assembly inserts, since in the 64-bit version of the Visual C ++ compiler the assembler is not supported. In this case, we either used intrinsic functions where we could do it, or rewrote C ++ code. In some cases, this did not lead to a performance degradation, for example, if in the 32-bit assembly code 64-bit MMX registers were used, then in the 64-bit version we already have all 64-bit registers.
How long does it take to fix 64-bit errors in such a project
At the beginning of work on a large project it is difficult to say how long it will take to port. Significant time at the first stage was taken by the assembly of third-party libraries, setting up the environment for the daily assembly of the 64-bit version and running the tests. When the work on the first part of the projects was completed, we were able to estimate the speed with which we are working, by the volume of the ported code for a certain period.
Examples of 64-bit problems we encountered
The most common mistake when porting to a 64-bit platform was explicitly casting pointers to 32-bit types, for example, to a DWORD. In such cases, the solution was a replacement for memsize-type. Code example:
MMRESULT m_tmScroll = timeSetEvent( GetScrollDelay(), TIMERRESOLUTION, TimerProc, (DWORD)this, TIME_CALLBACK_FUNCTION);
Errors also occurred when changing the parameters of virtual functions in the base class. For example, in CWnd :: OnTimer (UINT_PTR nIDEvent), the parameter type changed from UINT to UINT_PTR with the advent of the 64-bit version of Windows, and accordingly we also had to perform this replacement in all the heirs in our project. Code example:
class CConversionDlg : public CDialog { ... public: afx_msg void OnTimer(UINT nIDEvent); ... }
Some WinAPI functions support working with large amounts of data, such as CreateFileMapping and MapViewOfFile. And we adapted the code accordingly:
It was:
sharedMemory_ = ::CreateFileMapping( INVALID_HANDLE_VALUE,
It became:
#if defined(_M_IX86) DWORD sharedMemorySizeHigh = 0; DWORD sharedMemorySizeLow = sharedMemorySize; #elif defined(_M_X64) ULARGE_INTEGER converter; converter.QuadPart = sharedMemorySize; DWORD sharedMemorySizeHigh = converter.HighPart; DWORD sharedMemorySizeLow = converter.LowPart; #else #error "Unsuported build platform" #endif sharedMemory_ = ::CreateFileMapping( INVALID_HANDLE_VALUE,
Also in the project there were places for using functions, which in the 64-bit version are considered obsolete and should be replaced with the corresponding new implementations. For example, GetWindowLong / SetWindowLong should be replaced by GetWindowLongPtr / SetWindowLongPtr.
PVS-Studio finds all given examples and other types of 64-bit errors.
The role of the PVS-Studio static analyzer during 64-bit migration
Some of the potential errors when migrating to the 64-bit platform are found by the compiler and issue appropriate warnings. PVS-Studio is better at this task, since the tool was originally designed to find all such errors. More details about what 64-bit errors PVS-Studio finds and cannot find the compiler and the static analyzer of Visual Studio can be found in the article "
64-bit code in 2015: what's new in diagnosing possible problems? ".
I would like to draw attention to another important point. Regularly using a static analyzer, we could constantly observe how old old ones disappear, and sometimes new 64-bit errors are added. After all, the code is constantly ruled by dozens of programmers. And sometimes they can make a mistake and introduce a 64-bit error to a project that has already been adapted to x64. If it were not for the static analysis, it would be impossible to say how many errors were corrected, how many were made, and at what stage we are now. Thanks to PVS-Studio, we built graphics that helped us to have an idea of ​​progress. But this is a topic for a separate article.
Conclusion
In order for the 64-bit migration of your project to go as calmly as possible, the sequence of steps should be as follows:
- Examine the theory (for example, our articles).
- Find all 64-bit libraries that are used in the project.
- As quickly as possible to collect the 64-bit version, which is compiled and linked.
- Correct all 64-bit messages of the first level of the PVS-Studio analyzer (64 L1).
What to read about 64-bit migration?
- A collection of examples of 64-bit errors in real programs .
- Lessons learned from developing 64-bit C / C ++ applications .
- C ++ 11 and 64-bit errors
- 64-bit code in 2015: what's new in diagnosing possible problems?
If you want to share this article with an English-speaking audience, then please use the link to the translation: .