It was a wonderful summer day. Clouds were shining outside the window, crows were singing in gentle voices, someone's car was shaving fun with shampoo on a car wash, a puncher was quietly crouching behind the wall - in general, an idyll.
Nothing foreshadowed trouble exactly until I launched the program I just compiled in release mode to check the changes before submitting them to the testing department.
Prehistory
We have a company for a relatively long time, and the main product is already older than some of the company's employees, so there is enough ancient code. Nevertheless, we are trying to keep in a modern way, Modern C ++ is actively used, therefore about a year ago the main project was transferred to VC2015. It was a separate circus with horses, tambourines, blackjack and valerian. The secondary code translates as time and desire appear. In this case, I decided to transfer one of such auxiliary projects to VC2015, which is very actively used by our technical support.
')
I was sure that I already knew the pitfalls of such a transition, and that the task would take no more than an hour of work.
Externally, the program is simple: it shows a list of rows taken from different database tables. When a user selects a row, the column headings of the list change to the column names in the corresponding table.
And here I notice that this is not happening.
In vivo
I check several times, 100% reproducibility. I collect the project from scratch, I launch, I check. Zero to ground.
It was strange, because I remember exactly that the columns switched correctly during debugging, it was part of the test case. In order to make sure of my own responsibility, I launch the Debug version and see that the columns are switching. Also make sure that the error appears as soon as I turn on any optimization.
What a beautiful day.
The header update feature itself is pretty simple. At first, it considers some condition, and then something like this works:
int flag = !application->settings.showsize;
Please do not throw uranium slippers, I have already said that the code is quite old.
Thus, in any case, at least something, but it had to happen. Moreover, the debugger, getting to this code, simply jumps to the end of the function. I open the disassembler and see that this whole piece of code (and a dozen more lines before it) is simply missing in the listing. Of course, the optimized code sometimes looks more abruptly than any spaghetti, and even felt boots. But in this case, nowhere from the very beginning of the function is there even a hint of any transitions anywhere else. In the listing you can see the calculation of the condition, and immediately after it - the exit.
I begin to understand that the error is much more interesting than it seemed at the beginning, and the call of our specialist, who in the company is dealing with situations like “any unknown crap”, and who don’t feed with dinner - let me find some bug in Windows. We don’t have any illusions on the topic “there are no errors in compilers”, but experience suggests that 99.99% of “errors in compilers” are reduced to the wry hands of software developers.
In vitro
When an error occurs only in the optimized code, it may mean that we have stumbled upon an unknown UB somewhere, and the first candidate is the string
int flag = !application->settings.showsize;
It quickly turns out that the problem is really somewhere here, but "not everything is so simple." It was necessary to replace the expression with a constant, another variable, put the ternary operator instead of negation, or at least put the structure on the stack, as the code magically appeared in the listing.
In the absence of the best ideas, we pull out this function into a separate clean project and ruthlessly throw out all unnecessary. We immediately find out that shamanism with structures and pointers can be replaced with regular volatile:
#include <stdio.h> int main() { volatile int someVar = 1; const int indexOffset = someVar ? 0 : 1; // // const int indexOffset = !someVar; // // const int indexOffset = 0; // // const int indexOffset = 1; // // const int indexOffset = someVar; // // const int indexOffset = someVar + 1; // for (int i = 1 - indexOffset; i < 2 - indexOffset; ++i) { printf("Test passed\n"); } return 1; }
We were surprised a lot here, because in the original code the replacement of the negation by the ternary operator in the string
int flag = !application->settings.showsize;
returned a piece of code in place, but for volatile it no longer worked.
There was almost no doubt that we stumbled upon an error in the compiler, but it seemed incredible that there was no similar piece in tens of megabytes of code.
Investigation
It is worth noting that the main program is now going to vs2015 Update 2, because the program compiled in Update 3 suddenly became an antivirus curse, and that one that we install to our customers, and it could turn out ... ugly. However, some developers, including me, have Update 3 installed. We checked on several different computers and VS versions, and it turned out that the error was present only in Update 3. Later it turned out that it was more correct to write "starting from Update 3."
Google made it clear that he was not in the business, so the next logical step was to write
question on stackoverflow. I must say, to send a question to StackOverflow, starting with the phrase “We have found a mistake in the compiler” - it's like cutting your hand and jumping into the pool with sharks, but in this case the sharks turned out to be full and friendly. Literally in a few minutes, the test example was simplified even more, suggested a
tool that allowed you to see the result of the translation of this code by different compilers, and, more importantly, the magic phrase “
new SSA optimizer introduced in VS2015 Update 3 ” was
heard . The magic key -d2SSAOptimizer- was also mentioned there, disabling the new optimizer.
At this time, googling led us to
Introducing a new, advanced Visual C ++ code optimizer developer from the Visual C ++ Optimizer team with its coordinates and offering to send him error messages, which we used. And literally in 10-15 minutes we received the following answer:
Yes, this is definitely an error in the SSA-optimizer itself - usually most of the errors that are reported to us as optimizer errors are in other places and sometimes manifest themselves after 20 years just now.
A bug in a small optimization that tries to remove comparisons of the form (a - Const1) of a CMP (a - Const2) if no overflow occurs. The error occurs due to the fact that in your code there is an expression (1 - indexOffset) CMP (2 - indexOffset), and although the subtraction, of course, is noncommutative, the optimizer does not take this into account and processes (1 - indexOffset), as if ( indexOffset - 1).
A fix for this bug will be published in the next big update for VS2017. Until that time, a good solution could be to disable the SSA-optimizer for this function, if it does not cause a strong deceleration. This can be done using #pragma optimize ("", off): msdn.microsoft.com/en-us/library/chh3fb0k.aspxOriginalYes, it’s usually the bugs that have been exposed.
It's in a small opt. that if you are not overflow, CMP (a - Const2), if there is no overflow. (1 - indexOffset) CMP (2 - indexOffset) and the subtraction is not commutative, of course - but the optimizer has the same code as it’s (indexOffset - 1).
For update on VS2017. Until then, disabling the SSA Optimizer would be a decent workaround. It doesn’t slow down things too much. This can be done with #pragma optimize ("", off):
msdn.microsoft.com/en-us/library/chh3fb0k.aspx
Epilogue
As the investigation showed, at the moment all VC ++ compilers are subject to this error, starting with version 2015 Update 3 and ending with the most modern versions. It is not known yet when the fix will be released, so if you find that pieces of code miraculously disappear from your program, check, maybe the new optimizer decided that it needed it more than you?
It is somewhat disappointing that the fix will be released only for VS2017, however, we now know what to do about it.
What a wonderful day!
Thanks to
Codeguard for helping
me find this error.