Where can I learn about compilers and assert optimization? - c++

We discovered recently that compilers (for us it is GCC) can optimize some code in conjunction with asserts set by developers.
This code for example:
#include <cassert>
int getBatteryLevel(){
return 0;
}
int process(int level);
int main() {
[[maybe_unused]] const auto level = getBatteryLevel();
assert(level > 0);
process(level);
}
Will link with -O2 even if process has no implementation. It does not link without optimizations.
Is this documented anywhere?

Is this documented anywhere?
The documentation of optimisations isn't very thorough. The optimisations that are probably used here are "inline expansion", "constant folding" and "dead code elmination".
Where can I learn about compilers
Books are a good place to start, unless you intend to invent computing from ground up. Academic papers can also contain good information, but filtering out irrelevant stuff, and figuring out required preliminary knowledge can be a lot of work. Plus, they tend to be quite expensive per unit of information.
P.S. If you don't define the function that is odr-used, then the program is ill-formed. Language implementations are allowed to accept ill-formed programs, but are not required to. Usually, they are required to diagnose such bugs, but this particular case is where implementations are not required to diagnose.

Related

Evaluating an 'if' clause at compile time

Consider the following code snippet:
#include <limits>
#include <stdexcept>
void g(unsigned) {
// ...
}
template<typename UIntT>
void f(UIntT n)
{
if constexpr (std::numeric_limits<UIntT>::max() > std::numeric_limits<unsigned>::max())
{
if (n > std::numeric_limits<unsigned>::max())
throw std::length_error("Too long.");
}
g(n);
}
I wonder whether the 'if constexpr' clause is really useful here. Aren't compilers smart enough to find out whether the 'if' clause can ever be true for a given UIntT? If so, is this mandated by the standard?
Aren't compilers smart enough to find out whether the if clause can ever be true for a given UIntT?
Most are.
If so, is this mandated by the standard?
No, some optimizations have been given a name (RVO:s etc) and have later been incorporated into the language standard, but DEADC0DE optimizations aren't standardized (to my knowledge).
... but constexpr is
There's no way a conforming compiler would keep that block (if the condition is false) in your resulting binary - however you decide to optimize your code.
This use of if constexpr has no observable difference from an if according to the C++ standard.
However, slightly different variants of it could result in an observable difference in what symbols a compilation unit uses. It seems plausible to me that would cause observable differences.
Most modern compilers can and will reduce that to if (false) during optimization even if not constexpr, and dead-branch elimination is a pretty simple optimization. In a debug build they might leave the dead code alone, while they might eliminate it with constexpr.
Compiler explorer is great to answer specific cases of this kind of question, as it makes it pretty easy to see the generated assembly of every major compiler. So if you want to know if there is a difference in a default MSVC 2015 debug or release setup, you can see it there.

Why does TensorFlow recommends the "functional style for constructing operations"?

In TensorFlow's documentation, it is possible to find the following text:
// Not recommended
MatMul m(scope, a, b);
// Recommended
auto m = MatMul(scope, a, b);
I see no obvious benefit from using the "recommended" style. The first version is shorter at least. Also the "recommended" version might include more actions related to the unnecessary assignment operation.
I have read that documentation page no less than six times and still cannot get the rationale behind their reasoning.
Is this recommendation just a matter of style or may the second version have some benefits?
Also the "recommended" version might include more actions related to the unnecessary assignment operation.
There is no assignment. It's initialization. And any extra object that may exist in principle is elided entirely in any compiler worth using.
That recommendation you cite is inline with Herb Sutter's "Almost Always Auto" advice. The strongest point in favor of this style (which in full disclosure, I do not follow) is that it makes it impossible to leave a variable uninitialized.
auto foo; // ill-formed.
int foo; // indeterminate value
auto foo = int(); // a zero integer
Granted, a good static analysis tool can warn about it too, but it's still a very strong point that requires no additional analysis by a compiler or external tools.
Beyond that, the stylistic argument is that it also keep your code consistent with cases where you use auto for sanity's sake, such as
auto it = myVec.begin();
But YMMV on all counts. It is ultimately going to be a stylistic choice, and regardless of the chosen style, exceptions to both styles exist.

Quality of Visual Studio Community code analysis with SAL annotations

I hope this question is not out of scope for SO; if it is (sorry in that case), please tell me where it belongs and I'll try to move it there.
The concept of SAL annotations for static code analysis in C/C++ seems really useful to me. Take for example the wrongly implemented wmemcpy example on MSDN: Understanding SAL:
wchar_t * wmemcpy(
_Out_writes_all_(count) wchar_t *dest,
_In_reads_(count) const wchar_t *src,
size_t count)
{
size_t i;
for (i = 0; i <= count; i++) { // BUG: off-by-one error
dest[i] = src[i];
}
return dest;
}
MSDN says that "a code analysis tool could catch the bug by analyzing this function alone", which seems great, but the problem is that when I paste this code in VS 2017 Community no warning about this pops up on code analysis, not even with all analysis warnings enabled. (Other warnings like C26481 Don't use pointer arithmetic. Use span instead (bounds.1). do.)
Another example which should produce warnings (at least according to an answer to What is the purpose of SAL (Source Annotation Language) and what is the difference between SAL 1 and 2?), but does not:
_Success_(return) bool GetASmallInt(_Out_range_(0, 10) int& an_int);
//main:
int result;
const auto ret = GetASmallInt(result);
std::cout << result;
And a case of an incorrect warning:
struct MyStruct { int *a; };
void RetrieveMyStruct(_Out_ MyStruct *result) {
result->a = new int(42);
}
//main:
MyStruct s;
RetrieveMyStruct(&s);
// C26486 Don't pass a pointer that may be invalid to a function. Parameter 1 's.a' in call to 'RetrieveMyStruct' may be invalid (lifetime.1).
// Don't pass a pointer that may be invalid to a function. The parameter in a call may be invalid (lifetime.1).
result is obviously marked with _Out_ and not _In_ or _Inout_ so this warning does not make sense in this case.
My question is: Why does Visual Studio's SAL-based code analysis seem to be quite bad; am I missing something? Is Visual Studio Professional or Enterprise maybe better in this aspect? Or is there a tool which can do this better?
And if it's really quite bad: is this a known problem and are there maybe plans to improve this type of analysis?
Related: visual studio 2013 static code analysis - how reliable is it?
Functions contracts, of which SAL annotations are a lightweight realization, make it possible to reason locally about whether a function is doing the right thing and is used wrongly or the opposite. Without them, you could only discuss the notion of bug in the context of a whole program. With them, as the documentation says, it becomes possible to say locally that a function's behavior is a bug, and you can hope that a static analysis tool will find it.
Verifying mechanically that a piece of code does not have bugs remains a difficult problem even with this help. Different techniques exist because there are various partial approaches to the problem. They all have strengths and weaknesses, and they all contain plenty of heuristics. Loops are part of what makes predicting all the behaviors of a program difficult, and implementers of these tools may choose not to hard-code patterns for the extremely simple loops, since these patterns would seldom serve in practice.
And if it's really quite bad: is this a known problem and are there maybe plans to improve this type of analysis?
Yes, researchers have worked on this topic for decades and continue both to improve the theory and to transfer theoretical ideas into practical tools. As a user, you have a choice:
if you need your code to be free of bugs, for instance because it is intended for a safety-critical context, then you already have very heavy methodology in place based on intensive testing at each level of the V-cycle, and this sort of static analysis can already help you reach the same level of confidence with less (but some) effort. You will need more expressive contract specifications than SAL annotations for this goal. An example is ACSL for C.
if you are not willing to make the considerable effort necessary to ensure that code is bug-free with high-confidence, you can still take advantage of this sort of static analysis, but in this case consider any bug found as a bonus. The annotations, because they have a formally defined meaning, can also be useful to assign blame even in the context of a manual code review in which no static analyzer is involved. SAL annotations were designed explicitly for this usecase.

Why can't constexpr just be the default?

constexpr permits expressions which can be evaluated at compile time to be ... evaluated at compile time.
Why is this keyword even necessary? Why not permit or require that compilers evaluate all expressions at compile time if possible?
The standard library has an uneven application of constexpr which causes a lot of inconvenience. Making constexpr the "default" would address that and likely improve a huge amount of existing code.
It already is permitted to evaluate side-effect-free computations at compile time, under the as-if rule.
What constexpr does is provide guarantees on what data-flow analysis a compliant compiler is required to do to detect1 compile-time-computable expressions, and also allow the programmer to express that intent so that they get a diagnostic if they accidentally do something that cannot be precomputed.
Making constexpr the default would eliminate that very useful diagnostic ability.
1 In general, requiring "evaluate all expressions at compile time if possible" is a non-starter, because detecting the "if possible" requires solving the Halting Problem, and computer scientists know that this is not possible in the general case. So instead a relaxation is used where the outputs are { "Computable at compile-time", "Not computable at compile-time or couldn't decide" }. And the ability of different compilers to decide would depend on how smart their test was, which would make this feature non-portable. constexpr defines the exact test to use. A smarter compiler can still pre-compute even more expressions than the Standard test dictates, but if they fail the test, they can't be marked constexpr.
Note: despite the below, I admit to liking the idea of making constexpr the default. But you asked why it wasn't already done, so to answer that I will simply elaborate on mattnewport's last comment:
Consider the situation today. You're trying to use some function from the standard library in a context that requires a constant expression. It's not marked as constexpr, so you get a compiler error. This seems dumb, since "clearly" the ONLY thing that needs to change for this to work is to add the word constexpr to the definition.
Now consider life in the alternate universe where we adopt your proposal. Your code now compiles, yay! Next year you decide you to add Windows support to whatever project you're working on. How hard can it be? You'll compile using Visual Studio for your Windows users and keep using gcc for everyone else, right?
But the first time you try to compile on Windows, you get a bunch of compiler errors: this function can't be used in a constant expression context. You look at the code of the function in question, and compare it to the version that ships with gcc. It turns out that they are slightly different, and that the version that ships with gcc meets the technical requirements for constexpr by sheer accident, and likewise the one that ships with Visual Studio does not meet those requirements, again by sheer accident. Now what?
No problem you say, I'll submit a bug report to Microsoft: this function should be fixed. They close your bug report: the standard never says this function must be usable in a constant expression, so we can implement however we want. So you submit a bug report to the gcc maintainers: why didn't you warn me I was using non-portable code? And they close it too: how were we supposed to know it's not portable? We can't keep track of how everyone else implements the standard library.
Now what? No one did anything really wrong. Not you, not the gcc folks, nor the Visual Studio folks. Yet you still end up with un-portable code and are not a happy camper at this point. All else being equal, a good language standard will try to make this situation as unlikely as possible.
And even though I used an example of different compilers, it could just as well happen when you try to upgrade to a newer version of the same compiler, or even try to compile with different settings. For example: the function contains an assert statement to ensure it's being called with valid arguments. If you compile with assertions disabled, the assertion "disappears" and the function meets the rules for constexpr; if you enable assertions, then it doesn't meet them. (This is less likely these days now that the rules for constexpr are very generous, but was a bigger issue under the C++11 rules. But in principle the point remains even today.)
Lastly we get to the admittedly minor issue of error messages. In today's world, if I try to do something like stick in a cout statement in constexpr function, I get a nice simple error right away. In your world, we would have the same situation that we have with templates, deep stack-traces all the way to the very bottom of the implementation of output streams. Not fatal, but surely annoying.
This is a year and a half late, but I still hope it helps.
As Ben Voigt points out, compilers are already allowed to evaluate anything at compile time under the as-if rule.
What constexpr also does is lay out clear rules for expressions that can be used in places where a compile time constant is required. That means I can write code like this and know it will be portable:
constexpr int square(int x) { return x * x; }
...
int a[square(4)] = {};
...
Without the keyword and clear rules in the standard I'm not sure how you could specify this portably and provide useful diagnostics on things the programmer intended to be constexpr but don't meet the requirements.

Micro optimization - compiler optimization when accesing recursive members

I'm interested in writing good code from the beginning instead of optimizing the code later. Sorry for not providing benchmark I don't have a working scenario at the moment. Thanks for your attention!
What are the performance gains of using FunctionY over FunctionX?
There is a lot of discussion on stackoverflow about this already but I'm in doubts in the case when accessing sub-members (recursive) as shown below. Will the compiler (say VS2008) optimize FunctionX into something like FunctionY?
void FunctionX(Obj * pObj)
{
pObj->MemberQ->MemberW->MemberA.function1();
pObj->MemberQ->MemberW->MemberA.function2();
pObj->MemberQ->MemberW->MemberB.function1();
pObj->MemberQ->MemberW->MemberB.function2();
..
pObj->MemberQ->MemberW->MemberZ.function1();
pObj->MemberQ->MemberW->MemberZ.function2();
}
void FunctionY(Obj * pObj)
{
W * localPtr = pObj->MemberQ->MemberW;
localPtr->MemberA.function1();
localPtr->MemberA.function2();
localPtr->MemberB.function1();
localPtr->MemberB.function2();
...
localPtr->MemberZ.function1();
localPtr->MemberZ.function2();
}
In case none of the member pointers are volatile or pointers to volatile and you don't have the operator -> overloaded for any members in a chain both functions are the same.
The optimization rule you suggested is widely known as Common Expression Elimination and is supported by vast majority of compilers for many decades.
In theory, you save on the extra pointer dereferences, HOWEVER, in the real world, the compiler will probably optimize it out for you, so it's a useless optimization.
This is why it's important to profile first, and then optimize later. The compiler is doing everything it can to help you, you might as well make sure you're not just doing something it's already doing.
if the compiler is good enough, it should translate functionX into something similar to functionY.
But you can have different result on different compiler and on the same compiler with different optimization flag.
Using a "dumb" compiler functionY should be faster, and IMHO it is more readable and faster to code. So stick with functionY
ps. you should take a look at some code style guide, normally member and function name should always start with a low-case letter