Memory model

Boehm, Hans hans.boehm at hp.com
Fri Jan 28 19:38:55 GMT 2005


Herb -

We started thinking in a bit more detail about what a C++
memory model would look like.  I believe we need to make
a fairly fundamental decision at the outset, which depends
on other goals of the C++ committee.  We were hoping you might
be able to provide some guidance.  If Microsoft has a position
on this, it would also be good to understand that.

The basic options are:

1) We basically stick with the current pthreads approach of
leaving the behavior of races involving ordinary assignments
undefined.  (I believe this is also the compiler-writers view
of the world with win32 threads, outside of .NET,
though I haven't found documentation there.)  We would have
to do some work to pin down what this means.  And doing so
would impose some compiler constraints, e.g. no speculative
register promotion, and severe restrictions on overwriting
adjacent struct and class fields.  But it would minimize such
impact.

We would view atomic operations, and possibly volatile operations,
as synchronization actions.  Thus atomic operations (and possibly
volatile operation) not only could not result in a race, but
could also help to eliminate races between ordinary variables.
You could make double-checked locking
work in this model, but you would need at least a volatile
declaration.

This model is different from the Java model, and I think, the
intent of the .NET model.  It is incompatible with running untrusted
code in a secure application, since there is nothing to prevent
untrusted code from introducing a race, and hence invoking undefined
behavior.  But it seems to be much more in the spirit of current
standards, which already invoke undefined behavior in many places.

As a practical matter, it also means, for example, that if p
is a pointer to a vector of immutable size, then something
like

	my_p = p;
	if (i > 0 && i < my_p -> size()) x = p[i];

is allowed to still generate a subscript error (or do anything
else), if the pointer p can change concurrently, since the compiler may,
for example, reload my_p from p at any time, if it deems
that to be convenient.

2) We go with something much closer to the Java model, in
which races between ordinary variable accesses have semantics,
and the model tells you which of the possible memory writes
may be seen by a read.  I think this is a far more drastic and
pervasive change for compiler writers.  It would mean that the
compiler cannot introduce additional reads from globals, e.g.
to reload a spilled register.

I think that for this to be meaningful, we also need some
atomicity guarantees for ordinary variable accesses, especially
for pointers.  I'm concerned that C++ supports some embedded
architectures that can't efficiently support atomic pointer loads
and stores.  X86 implementations with unaligned pointers
probably wouldn't work anymore, but hopefully nobody does
that anyway.

For Java, this was much less of a change, since even the original
model required it.  And with potentially untrusted code in secure
applications, I think this is the only viable path.

----------------------------

Based on a prior conversation, at least Doug and I grudgingly
favor option 1.  It's probably not the route we would go if we were
designing a language from scratch.  But I don't think we can justify
the cost of option 2 unless the C++ committee as a whole wants to
move the language in that direction.  Unfortunately, I think
this affects many other design decisions.  Thus it would be nice
to, at least tentatively, resolve this soon.

Hans






More information about the cpp-threads mailing list