This is the mail archive of the libstdc++@gcc.gnu.org mailing list for the libstdc++ project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: c++/6546: static const members optimization failure


On Sat, Jun 29, 2002 at 09:57:28AM +0200, Gabriel Dos Reis wrote:
> Nathan Myers <ncm-nospam@cantrip.org> writes:
> | On Fri, Jun 28, 2002 at 08:42:29AM +0200, Gabriel Dos Reis wrote:
> | > Nathan Myers <ncm-nospam@cantrip.org> writes:
> | > | 
> | > |   struct foo { static const int bar = 3; };
> | > |   int f(int i) { return std::min(foo::bar, i); }
> | > | 
> | > | the compiler actually generates code to load the static value 
> | > | for foo::bar from memory each time (twice, in the code above), 
> | > | instead of using the manifest constant 3 in its place.  
> | > 
> | > std::min() expect an lvalue, foo::bar is such a thing, therefore its
> | > the designated object's address is taken; thyen its definition is
> | > required.  Anything after that is optimization, optimization that
> | > can't change the semantics.  Therefore the definition is required.

Gabriel, I suggested you study the case more carefully *not* because 
you disagreed, but because you neglected two key facts that would 
have led you to the correct conclusion.
 
> | The context is an inline 
> | function expanded in a context that doesn't need an lvalue.
> 
> No.  Firstly the declaration of std::min is this:
>   template<typename T> inline const T& min(const T&, const T&)
> which clearly expects an lvalue (when an lvalue is not provided, a
> temporary is created to hold the rvalue in order to satisfy that
> requirement, but you already knew that).
> 
> Therefore at the point of function call, foo::bar has to be defined --
> it is the compiler job to generate code in order to meet that
> requirement. 

False.  It is the compiler's job to enforce semantic requirements, and
(separately) to generate code that implements the required computation.
The distinction matters, as will be seen.
 
> Inlining appears later.  But inlining doesn't mean that the semantics
> restrictions can be violated.  Inlining isn't macro substitution.
>
> After inlining, the compiler can do constant propagation and replace
> foo::bar with its manifest value. But that doesn't invalidate previous
> semantics restrictions.

Here is where you neglect the first key fact.  Optimization is allowed
to crush out abstraction overhead.  Demanding an lvalue is detectable
solely in the linkage behavior; there is no way for a program to 
cause a different function to be executed based on whether the lvalue
is used as one.  (Note that these are necessarily built-in arithmetic
types.)  Therefore the optimizer is allowed -- and to correct the
regression, required -- to convert the lvalue reference to an rvalue
usage, e.g. compare-immediate and load-immediate instructions.  
Even if the lvalue reference were semantically required (about which 
more anon), that would not affect the correct choice of instructions to 
issue.

> | In fact, in gcc-2.95, the memory references were optimized away
> | just fine.  The compiler was entirely justified in eliding the
> | memory references.  
> 
> The compiler can elide memory reference, but requiring foo::bar to be
> defined is also a requirement of the language definition.  The fact
> that it failed to do so was a bug.

Here is the second place where Gaby neglected a key fact, leading
to a wrong conclusion.  In fact, the standard does *not* require
that an implementation report that foo::bar is not defined.  A
compiler is allowed simply to assume a definition, and the better 
ones do.

Some background is needed to understand how that came about.  
When the member "static const int i = 0" feature was proposed, 
by analogy to the similar enumeration, reasonable objections 
were raised that we already had semantics for static member 
data, and that the inline definition shouldn't change them.  

More to the point, vendors who depended on archaic linkers 
objected that it would be hard to implement without a requirement
for the user to provide a separate definition.  Modern implementers
countered that requiring a separate definition would be a nuisance,
as it provides no extra useful information to the compiler; it's
just tedious makework.  They compromised: the definition is formally
required, but the compiler is not required to diagnose its omission.

This bit of weasel-wording allowed "challenged" compilers to depend
on the user to generate the definition, while allowing modern compilers
to generate it themselves.  

This is similar to the template instantiation case: compilers
can require you to instantiate your templates manually, but the
good ones don't. 

Modern compilers use template machinery to generate the definition,
similarly to the way they generate an extern inline definition when
some bit of code takes the address of one. In fact, if the static
const member proposal had been considered at the time that extern
inlines were finally cleaned up, the outcome would probably have been
the same, as by then people felt more confident about requiring 
template-like semantics in non-template contexts.
 
> | The result was faster, correct code.  This makes it a regression.
> 
> I think you're confusing two things: 1) requiring foo::bar to be
> defined and 2) missing opportunities to apply constant propagation.
> 
> Issue 1) is definitely mandated.

False, as noted.

> Issue 2) is a QoI that can be viewed as a regression. 
> 
> Using 2) in order to write codes that violate 1) is asking for trouble
> and it isn't the compiler fault.  It is user's.

Therefore, also false.

The result is that, in fact, gcc-2.95 was correct in not demanding
a separate definition of the static member, and gcc-3 (while not
strictly nonconforming) has in fact regressed.

Nathan Myers
ncm at cantrip dot org

p.s. for reference, here is how the weasel-wording came out in the
standard: 

9.4.2 - Static data members [class.static.data]
-4- ...  the member can appear in integral constant expressions within 
its scope. The member shall still be defined in a namespace scope if it 
is used in the program and the namespace scope definition shall not 
contain an initializer.

-5- There shall be exactly one definition of a static data member that
is used in a program; no diagnostic is required; see basic.def.odr.  ...

The "no diagnostic" is what gives implementations the latitude to
mitigate the nuisance.  Since the standard doesn't say what a program
that lacks such a definition does, the implementation just has to 
document that it behaves as if the user had supplied the definition.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]