Introducing a new, advanced Visual C++ code optimizer
blogs.msdn.microsoft.com
blogs.msdn.microsoft.com
> Historically, Visual C++ did not take advantage of the
> fact that the C and C++ standards consider the result
> of overflowing signed operations undefined. Other
> compilers are very aggressive in this regard, which
> motivated the decision to implement some patterns which
> take advantage of undefined integer overflow behavior.
> We implemented the ones we thought were safe and didn’t
> impose any unnecessary security risks in generated code.
> A new undocumented compiler flag has been added to
> disable these optimizations, in case an application
> that is not standard-conformant fails:
> -d2SSAOptimizerUndefinedIntOverflow-. Due to security
> concerns, we have seen cases where these patterns
> should not be optimized, even though following the C
> and C++ standards allows us to [...]
It sounds like they're not doing anything that GCC/Clang aren't already, but if you've only ever compiled with MSVC then be careful not to overlook this.Edit: To add to my reply - for example, Java has almost no undefined behaviour. (The single exception AFAIK is some kind of multithreaded data race situation). The cost paid is that e.g. bounds checks must be done (in general) on array accesses, as the behaviour is defined to be an exception thrown in that case. Similarly for null reference dereferences.
I am coding since the mid-80's, always been a fan of Algol lineage of programming languages, with C++ being the only exception to it, as it allows many similar features.
Never have I had a situation where better performance thanks to undefined behaviour was an advantage for the use case we were trying to solve.
I think outside games, HPC and winning compiler benchmarks there hardly an advantage exploiting it.
That's not true. Load-load forwarding benefits tons of code that isn't "games, HPC, or compiler benchmarks".
If the solution delivered is within the time constrains required by the customer, any time spent optimizing ms out of the application is just wasted money.
Long story short: historical accident. Modern safe systems languages like Rust and Ada¹ avoid undefined behavior unless you explicitly ask for unsafe code, and even then there aren't many _undefined_ things you can do (violate strict aliasing I guess).
Most undefined behavior could be implementation-defined, but the edge cases from porting C+Unix from the PDP-11 (which I understand to have been... quirky) to many other systems happened before the spec, then the spec authors had to make the Ideal C Machine a sort of subset of all the existing behaviors. Thus, some things that could've been implementation-defined ended up totally undefined (god knows what computers did on signed overflow back then—the compiler may not have been able to guarantee anything: hence undefined) and we've kinda stuck that way ever since.
That said, the people who shame the compiler writers for "exploiting undefined behavior to optimize microbenchmarks and look good" annoy me to no end. That's just how the spec turned out, put up or shut up (or use a nicer language that isn't trying to stab you in the back as soon as you let down your vigilance...).
Here's a fun history of C: http://pastebin.com/UAQaWuWG
¹ Strictly speaking, I recall the Ada spec has some wording similar to undefined behavior, but it's not something you hit often.
> even then there aren't many _undefined_ things you can do
There's actually plenty of things you can do in an `unsafe` block in Rust that counts as undefined behavior (I don't know how you "explicitly ask for unsafe code" in Ada, but I'm confident the same is true there as well). Some of this falls out of the fact that Rust compiles down to LLVM IR, which itself has undefined behavior, but others are inherent (like demanding the preservation of the aliasing guarantees, as you mentioned).For anyone curious about seriously using unsafe code in Rust (which is to say, using unsafe code in Rust at all, in any capacity), I recommend reading through the Rustonomicon: http://doc.rust-lang.org/nightly/nomicon/
You need to import a virtual package and some times also make use of specific pragmas.
Ada, and Modula-3 are very much "into your face" in what concerns unsafe code.
For example, if you intend to do a conversion between data types without guarantee that the target can can hold a valid data representation, you need to import Ada.Unchecked_Conversion.
And do something like this:
http://www.adaic.org/resources/add_content/docs/95style/html...
Same applies to everything else deemed unsafe.
C was always a language that was intended to be used across multiple hardware targets and different processors have different behaviours. Restricting behaviour on certain arithmetic operations might require slow code to 'work around' the processor and generate compliant code. The creators didn't want that so specified as little as possible, leaving the rest to be 'Undefined'.
For example most CPUs wrap from most positive to most negative on integer overflow, but one kind might just clamp the value at the most positive value. Or give an error, so forcing them to behave in a given way would make it hard to write efficient C programs on some platforms.
Recently some compiler writers decided that they could interpret undefined behavior to mean "Do anything we like".
So for example a sane compiler on x86-64 would compile this :-
int test(int x)
{
return(x > x + 1)
}
into a code which adds one to a variable then compared it with the original, and this absolutely can return both false and true. However compiler writers have gone Aha! Overflow is undefined, so we'll "optimize" this into always returning false. That is faster code, who wouldn't want faster code!Personally I think this was never the intent of the standard and compiler writers are abusing the meaning of undefined in order to sneak in optimizations but realy they are just producing compilers that are less and less trustworthy as they no longer do that people want or need.
Edit: Also keep in mind that to disable these optimizations the complete flag is "-d2SSAOptimizerUndefinedIntOverflow-" minus quotations. That is it includes the dash/minus at the end to disable it.
If you write MSVC only code you can add it to your compiler options for the projects that use this new compiler.
If you write cross platform code this is (potentially) one less thing that is different between compilers.
initial: (106 * 17) / 17 = 10 (overflow) / 17 = 0
optimized: 106 * (17 / 17) = 106 * 1 = 106
So in this case the optimized version gives the expected result - it's still different than the initial expression, so it falls under the "undefined overflow" optimizations category.
It also sounds like they have something like LLVM's InstCombine pass, it'll be interesting to compare which cases they handle. Despite it being a peephole pass, InstCombine is actually one of the most important scalar optimizations in LLVM's arsenal.
I also wonder if they form SSA in the face of C++ and/or SEH exceptions.
Like if you have something like:
bool f() {
int x = 2;
try {
throw 0;
} catch (...) {
x = 4;
}
return x & 1;
}
Will they insert a PHI of 2 and 4?
The MSVC of today cannot optimize the return to a constant. $ clang t.cpp -target x86_64-pc-win32 -S -emit-llvm -o - | opt -S -sroa -instcombine | grep 'ret i1'
ret i1 false bool f(bool b) {
int x;
try {
if (b) {
x = 2;
throw 0;
}
x = 4;
throw 0.0;
} catch (...) {
}
return x & 1;
}
Here, a phi is needed on the catch.Out of curiosity, can you give details regarding how your EH representation looks like in SSA?
SSA is a program representation which is easy for compilers to analyze and optimize. See https://en.wikipedia.org/wiki/Static_single_assignment_form for more.
Their standard library (besides being intentionally not fully compliant) is missing some C11 features though. I guess we have to wait at least for C++17:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p006...
Clang/C2 is exactly the path for those that still care about C on Windows.
int test(unsigned char a) {
short b = a; // b: 00000000________, a: ________
b <<= 4; // b: 0000________0000
b |= 3; // b: 0000________0011
return b != 0; // -> return true
}
The analyzer doesn't need any information about the two low bits of b to know b != 0. Had it been b ^= 3 instead of b |= 3, it would be a different matter. * Visual C++ now has very aggressive SSA-based optimization rules.
* One of these optimizations is the bit estimator, which tracks the state of each bit of local variables.
I'm not aware of LLVM/GCC or other compilers using a bit estimator. I found some hits in the CS literature but I'm not aware of any other compilers using it, and it seems like a pretty potent optimization!A good comparison is CCP (conditional copy propagation) vs SCCP (sparse CCP) - the SCCP version is naturally flow-sensitive (that's a property of SSA form), so it can eliminate entire branches, leading to more dead code being eliminated.
int x = ...
if(x > 0) {
use(x)
else {
use(x)
}
In a DFA where you track information about each variable at each program point, you could push the constraint implied by the condition into each branch. If you do a sparse analysis on SSA form, that doesn't come naturally, right? x1 = ...
if (x1 > 0) {
x2 = pi(x1 & RANGE[1..])
use(x2)
} else {
x3 = pi(x1 & RANGE[..0])
use(x3)
}
x4 = phi(x2, x3) // if used
I have placed the pi nodes in the blocks, but semantically they are placed along the control edge.Ref e.g. e-SSA in the ABCD value range inference algorithm.
All of this stuff trickles down without a doubt. I realize it isn't going to magically optimize the bytecode, however, the thing that runs the bytecode will be faster.
As for the standards, there's pretty good partial support for C++11/14/17. One fairly recent blog post by them has some tables. [1]
[1] https://blogs.msdn.microsoft.com/vcblog/2016/01/22/vs-2015-u...
It is great for GUI applications, you get to develop C++ applications as if they were Delphi, VB ones, with all the power of C++.
For Microsoft, the lame MFC was already visual enough. To be faire to Microsoft the original MFC was much more powerful, .NET style, but many didn't like it that way, hence the thin layer over Win32 APIs[2].
However since Windows 8 and the introduction of Windows App Model, Visual C++ has actually become Visual. When developing Windows apps based on the new APIs introduced to replace Win32, you can develop pure native applicatins in C++ using XAML and the exactly the same RAD tooling as .NET WPF applications.
[1] https://www.embarcadero.com/products/cbuilder
[2] https://groups.google.com/forum/#!topic/microsoft.public.vc....