Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 13 May 2017 03:40:39 -0700
From:      Mark Millard <markmi@dsl-only.net>
To:        sgk@troutmask.apl.washington.edu
Cc:        Bruce Evans <brde@optusnet.com.au>, freebsd-hackers@freebsd.org, numerics@freebsd.org
Subject:   Re: catrig[fl].c and inexact
Message-ID:  <DC2DA938-6A07-4CB0-AFB6-038368971B77@dsl-only.net>
In-Reply-To: <20170513060803.GA84399@troutmask.apl.washington.edu>
References:  <20170512215654.GA82545@troutmask.apl.washington.edu> <20170513103208.M845@besplex.bde.org> <20170513060803.GA84399@troutmask.apl.washington.edu>

next in thread | previous in thread | raw e-mail | index | archive | help

On 2017-May-12, at 11:08 PM, Steve Kargl <sgk at =
troutmask.apl.washington.edu> wrote:

> On Sat, May 13, 2017 at 11:35:49AM +1000, Bruce Evans wrote:
>> On Fri, 12 May 2017, Steve Kargl wrote:
>>=20
>>> So, I've been making improvements to my implementations of
>>> the half-cycle trig functions.  In doing so, I decide to
>>> add WARNS=3D2 to msun/Makefile.  clang 4.0.0 dies with an
>>> error about an unused variable in raise_inexact() from
>>> catrig[fl].c.
>>>=20
>>> /usr/home/kargl/trunk/math/libm/msun/src/catrigl.c:195:2: error: =
unused variable
>>>     'junk' [-Werror,-Wunused-variable]
>>>       raise_inexact();
>>>       ^
>>> /usr/home/kargl/trunk/math/libm/msun/src/catrigl.c:56:45: note: =
expanded from
>>>     macro 'raise_inexact'
>>> #define raise_inexact() do { volatile float junk =3D 1 + tiny; } =
while(0)
>>>                                           ^
>>> Grepping catrig.o for the variable 'junk' suggests that 'junk' is
>>> optimized out (with at least -O2).
>>=20
>> Just another bug in clang.  Volatile variables cannot be optimized =
out
>> (if they are accessed).
>=20
> Does this depend on scope?  'junk' is local to the do {...} while(0);
> construct.  Can a compiler completely eliminate a do-nothing scoping
> unit?  I don't know C well enough to know.  I do know what I have
> observed in clang.

[This note ignores other standards than C99/C11
that might place other constraints. And I've done
no checking of compiler results, I've just looked
at a couple of the C standards.]

Note: I've not looking to tiny's declaration. It
may contribute in a way not covered below.

Unfortunately the declarator in an init-declarator
that has an initializer is not part of an
expression. The rules for volatile are tied to uses
in expressions, not to the declarator. (Which is a
hole in the language definition as far as I can
tell.)

There is one part of the wording that might mitigate
this, tied to a full declarator having a sequence
point at its end despite the declarator itself not
being an expression, even if its initializer is
one. There is another wording detail that might
as well.

Still, overall it would seem safer to be sure there
is an expression that references the volatile object,
not having only its declarator. But I would not take
even that as a guarantee under the C standards.

It may seem a silly difference but:

do { volatile float junk=3D1; junk+=3Dtiny; } while(0)

may well be a better way of writing the "must
evaluate" part of the intent simply because
junk is used in an expression. Also it has both read
and write access, so is a little more "used". The
sequence point before the assignment can help avoid
compile-time evaluation as well.


Details if you care. . .

I used the C99 and C11 definitions here, I
reference C11 section numbering but C99 agrees
as I remember.

5.1.2.3 Program execution says:

"Accessing a volatile object, modifying an object,
modifying a file, or calling a function that does
any of those operations are all side effects,
which are changes in the state of the execution
environment. Evaluation of an expression may
produce side effects."

Note that raising inexact does not fit in the
definition of side effect as far as I can tell.
So a compiler need not consider such a thing
for side-effect issues if I understand right.

[C11 specific wording:] "The presence of a
sequence point between the evaluations of
expressions A and B implies that every value
computation and side effect associated with A
is sequenced before every value compuation and
side effect associated with B."

[C99 is similar but is before the detailed
"sequenced before" definition.]

"An actual implementation need not evaluate part
of an expression if it can deduce that its value
is not used and that no needed side effects are
produced (including any caused by calling a
function or accessing a volatile object)."

Can a accessing a volatile object ever be
classified as having "no needed side effects"?
More on this later. [Remember what "side effect"
excludes, as noted earlier. So some consequences
need not be considered by the compiler, all in
the name of optimizations.]

6.7.3 Type Qualifiers says:

"An object that has volatile-qualified type . . .
Therefore any expression referring to such as object
shall be evaluated strictly according to the rules
of the abstract machine, as described in 5.1.2.3.
Furthermore, at every sequence point the value last
stored in the object shall agree with that prescribed
by the abstract machine, except as modified by the
unknown factors mentioned previously. What constitutes
an access to an object that has volatile-qualified
type is implementation-defined."

This part is mixed: what the sequence point wording
giveth the last sentence taketh away. (More later.)

It also says in a note (134):

"A volatile declaration may be used to describe an
object corresponding to a memory-mapped input/output
port or an object accessed by an asynchronously
interrupting function. Actions on objects so declared
shall, not be "optimized out" by an implementation
or reordered except as permitted by the rules for
evaluating expressions."

Since rules for evaluating expressions are not rules
for declarators (vs. initializers), this could be
read as not allowing the "optimize out". (But the
abstract machine's description is not explicit about
declarators for such issues.)

The C99 Rationale:

The C99 Rationale was explicit about static
volatile for a memory mapped I/O register,
static const volatile for a memory mapped
input port, const volatile and volatile
for variables shared across processes. To
some extent this identifies examples of
contexts with "needed side effects" that
have hardware details to take into account.

For taking into account hardware details:
". . . Whatever decision are adopted on such
issues must be documented, as volatile access
is implementation-defined".

For volatile use with no explicitly identified
hardware details: volatile would appear to be
no more than a potential hint for such a
context, not an effective requirement. The
implementation-defined status could allow lack
of access.

Overall, based on what I see in the C99 and
C11 language definitions, I'd not be willing to
declare clang wrong (if it did optimize out junk),
even with my alternative formulation.

C does not have an explicit Principle of Least
Astonishment as a official guideline to its
interpretation and the rules are very biased to
allowing so-called optimizations. "junk" does not
fit with being shared across processes (for
example its address is not handed to anything)
and is not static or even global. There is no
known type of potential context for specific
hardware details that would need to be taken
into account for junk. That in turn leaves open
not accessing it at all as far as I can tell.


=3D=3D=3D
Mark Millard
markmi at dsl-only.net




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?DC2DA938-6A07-4CB0-AFB6-038368971B77>