4

Suppose my code has the following function:

inline int foo() {
    bar();
    int y = baz();
    return y;
}

and suppose that bar() and baz() have side-effects.

If I write:

int z = foo();
printf("z is %d\n", z);

then obviously both bar() and baz() have to be executed. But if I write:

foo();

and not use the return value, the only reason to call baz() is for its side effects.

Is there a way I can tell my compiler (for any of: GCC, clang, MSVC, Intel) something like "you may ignore the side effects of baz() for purposes of deciding whether to optimize it away"? Or "you may ignore the side effects of this instruction for purposes of deciding whether to optimize it away"?

This question is phrased for C and C++ but could be relevant for all sorts of procedural languages. Let's make it C++ because that's what I use the most right now.

Note:

  • bar(), baz() are not under my control.
  • The signature of foo() may not be altered. Of course I could use some proxy and apply baz() lazily, if I so wanted; or just write a different function for the case of not using y. That's not what I'm asking.
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • You could pass an optional output paramter to the function and only call `baz` only if it's not `NULL`. It's certainly not as nice as `int z = foo();`, but could do the trick. – IlCapitano Oct 02 '20 at 20:43
  • 1
    Presumably `foo` would have to be an inline function or you have some really aggressive lto, otherwise the separate compilation model model makes it unlikely the compiler will assume anything about how the caller uses the return value. – StoryTeller - Unslander Monica Oct 02 '20 at 20:45
  • @NateEldredge: Links to info about "pure" or "const" as an attribute? Also, what if `bar()` and `baz()` are not under my control and come from `some_lib.h`? – einpoklum Oct 02 '20 at 20:47
  • @StoryTeller-UnslanderMonica: Made `foo()` inline, although otherwise the question would simply have been about intra-translation-unit optimization. – einpoklum Oct 02 '20 at 20:49
  • @IlCapitano: I could also write another function which doesn't call `baz()`... no cheating :-P – einpoklum Oct 02 '20 at 20:50
  • Far too late in the evening for me to attempt an answer, but to set someone on their way: this looks like something you could solve with *actors*. See Boost Phoenix. Briefly, build something to contain the function, then the function is only called if a user conversion operator is called, which of course, only happens with an assignment or similar. Or resort to a compiler extension if you're willing to give up portability. – Bathsheba Oct 02 '20 at 21:08
  • 1
    @Bathsheba: That might change the timing of the side effects, including their relative timing. Also, so the note at the bottom of the question. – einpoklum Oct 02 '20 at 21:13
  • @einpoklum: Yes, that's a good point. – Bathsheba Oct 03 '20 at 11:33

2 Answers2

5

As an extension to standard C and C++, gcc and clang support the pure function attribute which informs the compiler that the function has no side effects and unneeded calls can be deleted. If you declare a function with this attribute, the compiler will believe you, even if you are lying.

#include <iostream>

static int foo() __attribute__((pure));

static int foo() {
  std::cout << "I am a side effect!" << std::endl;
  return 17;
}

int main() {
  foo();
  return 0;
}

This prints no output. Note that clang will give a warning that you are ignoring the return value of a pure function.

(Peculiarly, when you do something similar in C, gcc optimizes out the call with -O0, but leaves it in with -O2!)

The definition of the function does not need to be in scope for this to work, and you can re-declare a function with this attribute even if a previous declaration is already in scope. So it can also be used for library functions. Example.

Of course, lying to the compiler is always at your own risk, and conceivably this could have potential ill effects that I haven't thought of. It may be a useful hack, if you are willing to carefully scrutinize the generated code to make sure it is really doing what you want, but I wouldn't trust it too far.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • `Peculiarly, when you do` Because it's `static`, it got inlined in main before `pure` was considered (that's how I understand it). If you remove `static` or add `noinline` then it gets removed for me. – KamilCuk Oct 02 '20 at 21:02
  • If a function is declared "pure" despite having side effects, is behavior limited to having the compiler call the function as often or as rarely as it it sees fit, or would the qualifier be seen as an invitation for the compiler to generate nonsensical code whether or not any of the side effects would otherwise matter? – supercat Oct 02 '20 at 21:37
  • @supercat: Yes, good question, I don't know. I'm not aware that the compiler uses the `pure` attribute for anything except eliding unnecessary calls, but it's possible that it does something else that could break. I would definitely categorize this as a hack, which I think is inevitable given that you are trying to get the compiler to generate code that doesn't behave as it is normally defined to. – Nate Eldredge Oct 03 '20 at 17:20
  • @NateEldredge: In many cases, knowing that a compiler will ignore certain things it should have no reason to care about will allow programmers to write code more clearly and efficiently than would otherwise be possible. Treating hinting attributes or qualifiers as inviting particular optimizations without regard for whether they would *adversely* affect program behavior will allow them to be applied much more often than treating them as inviting completely arbitrary behavior in cases where their effects might be observable. – supercat Oct 03 '20 at 18:07
0

There are a fair number of situations in which side effects of a function will only be relevant if the return value ends up being used, but as far as I can tell, neither C nor C++ has any means of indicating them.

As a common example, it may be useful to have routines which perform some computations and return the results, but will raise a trap if (e.g. because of overflow) they are unable to return a result that was not observably wrong. Depending upon how a function is being used, having it trap may be infinitely preferable to having return an observably-wrong answer, but might not be preferable to having it behave in a manner indistinguishable from having somehow yielded the correct answer. If a program performs a dozen computations, but only uses ten of them, skipping the unused computations entirely would likely be more efficient than generating code to see if overflow would occur within them.

Perhaps what would be helpful would be a construct to test whether a compiler can guarantee that the value of a particular lvalue will never observably affect program behavior; an implementation would always be allowed to say that it can't, but in situations where a compiler could offer such a guarantee, it would allow suitably-written code to say, e.g. "If the value of x, which this function is going to return, will never end up being used, don't bother to compute it; otherwise, check for overflow and trap if it would occur; if the return value is used and there would be no overflow, compute the value of the function."

I'm unaware of any C implementations that offer such a feature, however.

supercat
  • 77,689
  • 9
  • 166
  • 211