[cpp-threads] Prism 0.9.1, and further on the effects of races

Herb Sutter hsutter at microsoft.com
Fri Sep 22 03:57:58 BST 2006


Peter wrote:
> > Here's a different and maybe more useful way to put this question:
> > Should line c be allowed any additional behaviors than if global_ptr
> > pointed to a type having no virtual functions?
>
> I don't see why there should be any difference. The vptr is just a hidden
> member of the object. Consider:
[an example with an explicit program-declared member of pointer type]

The key word is "hidden." The vptr is not a member of the object as the object is declared in the program source code, and I do think that's a material difference. In the other email I sent in response to Hans, I compared it to a COW string; if the string implementer generates additional variables and machinery, it's his job to 'hold the caller harmless' -- make the caller no worse off than he would be if the implementer hadn't generated the fancy machinery. Put another way, implementation details shouldn't affect semantics.

Consider: If we go down the path of treating vptrs as UD-bait, then it's perilous even to dereference atomic<T*> in the current proposal. In particular, any virtual function call could cause a wild branch, even though there's not a function pointer in sight in the source code (and note that at test time there may not even be a vptr there, but it could get mixed in later, e.g., via inheritance from a template parameter).

If that's what's being proposed, does anyone really feel good about saying that calling any virtual function through an atomic<T*> is undefined behavior?

Am I misunderstanding? If yes, I'd appreciate help understanding what I'm missing. Thanks again for all the illumination to date, it's much appreciated.

Herb


> -----Original Message-----
> From: cpp-threads-bounces at decadentplace.org.uk [mailto:cpp-threads-
> bounces at decadentplace.org.uk] On Behalf Of Peter Dimov
> Sent: Thursday, September 21, 2006 6:49 PM
> To: C++ threads standardisation
> Subject: Re: [cpp-threads] Prism 0.9.1, and further on the effects of
> races
>
> Herb Sutter wrote:
> > (Sorry for the lag, I'm on the road and email is intermittent.)
> >
> > Peter wrote:
> >> Herb Sutter wrote:
> >>>
> >>>   thread 1: global_ptr = new Derived();
> >>>
> >>> With no constraints, this could execute as:
> >>>
> >>>             global_ptr = (Derived*)operator new( sizeof(Derived) );
> >>>             global_ptr->vptr = &Base::vtable;     // a
> >>>             global_ptr->Base ctor body;
> >>>             global_ptr->vptr = &Derived::vtable;  // b
> >>>             global_ptr->Derived ctor body;
> >>>
> >>> And in a race we perform:
> >>>
> >>>   thread 2: global_ptr->DerivedFunc();            // c
> >>
> >> This isn't a problem in either model A (undefined behavior) and
> >> model B (global_ptr has an undefined value in the event of a race).
> >> In both cases, the (c) statement is allowed to trap.
> >>
> >> It is only a problem if a read that participates in a race is
> >> constrained to
> >> only return one of the possible values that the variable could hold
> >> if the race was resolved in a sequentially consistent manner, but
> >> this is generally
> >> not true for non-atomic variables (if it were, we wouldn't need raw
> >> atomics.)
> >
> > Good points. For a moment, let's restrict to the case where
> > global_ptr is a type that is known to be atomically read/written --
> > either a proposed std::atomic<Derived*> or a plain Derived* on a
> > platform that happens to document a guarantee that reads and writes
> > from a plain default-aligned T* are atomic.
> >
> > In line c, the behaviors that I think will be unsurprising to users
> > are: (1) a correct function call on a completely constructed object,
> > and (2) a null pointer trap.
> >
> > The question is, should we also permit general undefined behavior,
> > such as a wild branch?
>
> If the store to global_ptr does not have release semantics, the behavior
> is
> undefined since thread 2 is not required to observe the updates to
> *global_ptr because they can become visible after the update to
> global_ptr.
>
> If the store to global_ptr does have release semantics, on all hardware
> except Alpha thread 2 will observe either NULL or a fully constructed
> object
> because of the data dependency between global_ptr and *global_ptr.
> However,
> if Derived::Derived updates a global variable as a side effect, this
> update
> might not become visible to thread 2, unless the read from global_ptr has
> acquire semantics.
>
> I think.
>
> > Here's a different and maybe more useful way to put this question:
> > Should line c be allowed any additional behaviors than if global_ptr
> > pointed to a type having no virtual functions?
>
> I don't see why there should be any difference. The vptr is just a hidden
> member of the object. Consider:
>
> struct Derived
> {
>     int * p;
>     int q;
>
>     Derived(): p( &q ), q( 5 ) {}
>     void DerivedFunc() { std::cout << *p << std::endl; }
> };
>
> If the store to global_ptr doesn't have release semantics,
> global_ptr->DerivedFunc() can easily access an uninitialized p and trap.
>
>
> --
> cpp-threads mailing list
> cpp-threads at decadentplace.org.uk
> http://www.decadentplace.org.uk/cgi-bin/mailman/listinfo/cpp-threads



More information about the cpp-threads mailing list