Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can the C compiler optimizer violate short-circuiting and reorder memory accesses for operands in a logical-AND expression?

We know that logical-AND operator (&&) guarantees left-to-right evaluation.

But I am wondering if the compiler optimizer can ever reorder the memory access instructions for *a and b->foo in the following code, i.e. the optimizer writes instructions that try to access *b before accessing *a.

(Consider both a and b to be pointers to memory regions in the heap.)

if (*a && b->foo) {
  /* do something */
}

One might think that && causes a sequence point, so the compiler must emit instructions to access *a before accessing *b but after reading the accepted answer at https://stackoverflow.com/a/14983432/1175080, I am not so sure. If you look at this answer, there are semi-colons between statements and they also establish sequence points and therefore they should also prevent reordering, but the answer there seems to indicate that they need compiler level memory barrier despite the presence of semicolons.

I mean if you claim that && establishes a sequence point, then that is true for semicolons in the code at https://stackoverflow.com/a/14983432/1175080. Then why is a compiler-level memory barrier required in that code?

like image 350
Lone Learner Avatar asked Jun 27 '16 18:06

Lone Learner


People also ask

What type of operator is&&?

The logical AND operator ( && ) returns true if both operands are true and returns false otherwise.


2 Answers

The system can evaluate b->foo until such time as it hits something that exceeds its ability to execute speculatively. Most modern systems can handle a speculative fault and ignore the fault if it turns out that the results of the operation are never used.

So it's purely up to the capabilities of the compiler, CPU, and other system components. So long as it can ensure there are no visible consequences to conforming code, it can execute (almost) anything it wants (almost) any time it wants.

like image 159
David Schwartz Avatar answered Sep 20 '22 02:09

David Schwartz


But I am wondering if the compiler optimizer can ever reorder the memory access instructions for *a and b->foo in the following code, i.e. the optimizer writes instructions that try to access *b before accessing *a.

if (*a && b->foo) {
  /* do something */
}

The C semantics for the expression require that *a be evaluated first, and that b->foo be evaluated only if *a evaluated to nonzero. @Jack's answer provides the basis for that in the standard. But your question is about optimizations that compiler performs, and the standard specifies that

The semantic descriptions in this International Standard describe the behavior of an abstract machine in which issues of optimization are irrelevant.

(C2013, 5.1.2.3/1)

An optimizing compiler can produce code that does not conform to the abstract semantics if it produces the same external behavior.

In particular, in your example code, if the compiler can prove (or is willing to assume) that the evaluations of *a and b->foo have no externally visible behavior and are independent -- neither has a side effect that impacts the evaluation or side effects of the other -- then it may emit code that evaluates b->foo unconditionally, either before or after evaluating *a. Note that if b is NULL or contains an invalid pointer value then evaluating b->foo has undefined behavior. In that case, evaluation of b->foo is not independent of any other evaluation in the program.

As @DavidSchwartz observes, however, even if b's value may be null or invalid, the compiler may still be able to emit code that speculatively proceeds as if it were valid, and backtracks in the event that that turns out not to be the case. The key point here is that the externally-visible behavior is unaffected by valid optimizations.

like image 37
John Bollinger Avatar answered Sep 23 '22 02:09

John Bollinger