Exporting information from a cancelled transaction

77 views
Skip to first unread message

Jens Maurer

unread,
Mar 3, 2013, 5:22:48 PM3/3/13
to t...@isocpp.org

I'm still catching up on last month's e-mail traffic, but so far
I'm still missing this vital bit of semantics:

What can be done to export information from an (explicitly)
cancelled transaction, e.g. to indicate the reason for the cancel
to outer code for recovery?

We can obviously distinguish whether a transaction was
cancelled or committed by using something like

bool __cancelled = true;

transaction_atomic {
__cancelled = false;
// transaction body, not touching __cancelled
}

// "__cancelled" has the value true if and only if tx was cancelled

so this is the minimum amount of information we can export.

At the other end of the range, we know it's going to be impossible
to reliably transfer objects carrying shared ownership from a
cancelled transaction, e.g. a std::shared_ptr<>.

Do we have an idea what can be reasonably implemented to export
more than the minimum? Do we have a rough idea how the C++ syntax
for such "cancel-survival-info" would look like?


We can ignore that question for the time being, but then we should
also punt on support for advanced recovery schemes.


Jens

Mark Moir

unread,
Mar 3, 2013, 8:00:19 PM3/3/13
to t...@isocpp.org

Hi Jens

On 3/4/13 11:22 AM, Jens Maurer wrote:
>
> I'm still catching up on last month's e-mail traffic, but so far
> I'm still missing this vital bit of semantics:
>
> What can be done to export information from an (explicitly)
> cancelled transaction, e.g. to indicate the reason for the cancel
> to outer code for recovery?

If/when you do catch up on the email, you will see that we did not make
progress on explicit cancel and have decided to go with a minimal
proposal for now, in which the only way a transaction can get canceled
is if it is annotated as [[cancel_on_escape]] and if an exception
escapes from it.

Thus, there is no special/new mechanism for communicating from the point
at which we decide to cancel a transaction to the transaction being
canceled. People can use whatever convention they like, such as
encoding information in exceptions, return values, etc. They will have
to ensure it is safe to execute out to the transaction boundary even
though the transaction will be canceled.

That's just for clarification, as the issue you are discussing of course
still applies when an exception escapes a [[cancel_on_escape]] transaction.

>
> We can obviously distinguish whether a transaction was
> cancelled or committed by using something like
>
> bool __cancelled = true;
>
> transaction_atomic {
> __cancelled = false;
> // transaction body, not touching __cancelled
> }
>
> // "__cancelled" has the value true if and only if tx was cancelled
>
> so this is the minimum amount of information we can export.

Yes, this is mentioned as a useful idiom in the current spec. I am not
sure if your use of double underscore in __cancelled suggests that you
mean it should be provided by the language, but this is not necessary:
programmers can use this idiom when they want to, using whatever
variable name they choose. However, it's not really needed in the short
term, as the only way to cancel a transaction is to annotate it as
cancel_on_escape, so the fact that it throws an exception implies that
it was canceled (and if it doesn't it wasn't).

>
> At the other end of the range, we know it's going to be impossible
> to reliably transfer objects carrying shared ownership from a
> cancelled transaction, e.g. a std::shared_ptr<>.
>
> Do we have an idea what can be reasonably implemented to export
> more than the minimum? Do we have a rough idea how the C++ syntax
> for such "cancel-survival-info" would look like?

I don't think so. I mentioned in email last week that I recalled Dave
Abrahams had some ideas by which programmers could explicitly "bless" a
given exception type as safe to survive transaction cancellation,
possibly by providing a special copy constructor for that purpose. I
hoped that Dave would recall this and weigh in with details, but he
doesn't and is too busy so he doesn't expect that he will.

I like this idea, because I don't think it's in the spirit of C++ to
prevent programmers from doing things just because they doesn't make
sense in all cases. If programmers want to throw exceptions that
include pointers to data structures that were *not* allocated within the
transaction, there is no problem with that, so why should we say they
can't just because we're not sure if they may have allocated those data
structures within the transaction?

In fact, I can even imagine reasonable uses in which an exception
contains pointers to things that *are* allocated in the to-be-canceled
transaction. For example, it could be useful to know which and how many
of the pointer fields in the exception object were modified during the
transaction, even if I know I can't go and access the data that they
point to. I am not suggesting this is necessarily a good practice that
should be encouraged, just pointing out that it's not true that anyone
who wants to know the value of a pointer in an exception object after it
escapes is confused, even if the pointer was modified to point to
something that is no longer accessible.

That said, it's clearly something one should do only carefully, so I
think it makes sense for programmers to explicitly say how different
exception types should be handled, for example via special copy
constructors.

Does anyone else have any thoughts along these lines?

Other alternatives we've discussed include a language-specified subset
of exception types that are automatically safe (e.g., "integral or
enumerated type" is the definition in the current draft). In an
"exceptions must be blessed in order to escape cancelled transactions"
regime, we could consider such a subset of exceptions to be implicitly
blessed, so explicit blessing would be needed only for other exception
types.

What happens if a "nonblessed" exception tries to escape a canceled
transaction? Alternatives include termination and converting the
exception to a special "NonBlessedExceptionTriedToEscape" exception.

I don't feel strongly either way about it really. People who don't like
the outcome (either outcome) can change their code so it won't happen,
for example by catching the about-to-escape exception at the transaction
boundary and throwing a blessed exception instead.

>
> We can ignore that question for the time being, but then we should
> also punt on support for advanced recovery schemes.

Not sure what you mean here, can you elaborate?

Cheers

Mark


>
>
> Jens
>

Dave Abrahams

unread,
Mar 4, 2013, 12:35:57 AM3/4/13
to Mark Moir, t...@isocpp.org

on Sun Mar 03 2013, Mark Moir <mark.moir-AT-oracle.com> wrote:

> I like this idea, because I don't think it's in the spirit of C++ to
> prevent programmers from doing things just because they doesn't make
> sense in all cases. If programmers want to throw exceptions that
> include pointers to data structures that were *not* allocated within
> the transaction, there is no problem with that, so why should we say
> they can't just because we're not sure if they may have allocated
> those data structures within the transaction?
>
> In fact, I can even imagine reasonable uses in which an exception
> contains pointers to things that *are* allocated in the to-be-canceled
> transaction. For example, it could be useful to know which and how
> many of the pointer fields in the exception object were modified
> during the transaction, even if I know I can't go and access the data
> that they point to. I am not suggesting this is necessarily a good
> practice that should be encouraged, just pointing out that it's not
> true that anyone who wants to know the value of a pointer in an
> exception object after it escapes is confused, even if the pointer was
> modified to point to something that is no longer accessible.
>
> That said, it's clearly something one should do only carefully, so I
> think it makes sense for programmers to explicitly say how different
> exception types should be handled, for example via special copy
> constructors.
>
> Does anyone else have any thoughts along these lines?

My thought was that at the transaction boundary, TM bookkeeping would be
turned off and the exception object could be copy-constructed into
memory that's visible outside the transaction.

--
Dave Abrahams

Jens Maurer

unread,
Mar 4, 2013, 1:34:20 AM3/4/13
to t...@isocpp.org
On 03/04/2013 06:35 AM, Dave Abrahams wrote:
> My thought was that at the transaction boundary, TM bookkeeping would be
> turned off and the exception object could be copy-constructed into
> memory that's visible outside the transaction.

That doesn't seem to work with the obvious HTM implementation,
where the CPU automatically turns every ordinary memory access into
a speculative operation (while inside a transaction block).

So we need some kind of special instrumentation for the "copy exception
object" to turn off the TM bookkeeping, it seems.

Jens

Mark Moir

unread,
Mar 4, 2013, 1:45:33 AM3/4/13
to t...@isocpp.org
To my knowledge, this would not be supported in general by any HTM on
the horizon. For example, Haswell's HTM allows an 8-bit "abort code" to
survive the canceled hardware transaction, IIRC. This would allow for
only very simple/small exceptions (much more constrained even than the
current spec). Even though it's very constrained, I think it will be
worthwhile to use such small exceptions (or perhaps explicit
cancellation return codes, in case we disentangle exceptions and
cancellation) in order to get the benefits of transactions that can be
canceled cheaply.

Alternatively, *perhaps* in some cases the compiler would be able to map
8-bit abort codes to a set of possible exceptions that could be thrown
from a canceled transactions. I think this will work great in some
cases, but clearly is not likely to provide a general solution.

Other than that, I think transactions that cancel and want to emit
larger exceptions will usually need to be executed in software, at least
unless and until HTM provides support for getting larger amounts of
information out of canceled transactions.

Cheers

Mark

Dave Abrahams

unread,
Mar 4, 2013, 1:51:57 AM3/4/13
to Mark Moir, t...@isocpp.org

on Sun Mar 03 2013, Mark Moir <mark.moir-AT-oracle.com> wrote:

Would it be reasonable to start the transaction with hardware support,
but re-try it with STM when the exception reaches the transaction
boundary?

--
Dave Abrahams

Mark Moir

unread,
Mar 4, 2013, 1:54:03 AM3/4/13
to t...@isocpp.org
Yes, I assume this would be the default approach unless we had some
information indicating that the hardware transaction was likely to fail,
or to throw an exception (and therefore need to be canceled).

Cheers

Mark


Justin Gottschlich

unread,
Mar 4, 2013, 2:02:59 AM3/4/13
to t...@isocpp.org
This is my understanding, too; that is, try a hardware transaction
first, fail due to an exception, then retry as a software transaction
and emit the exception (assuming it still happens).

Justin


>
> Cheers
>
> Mark
>
>
> --
> You received this message because you are subscribed to the Google Groups
> "SG5 - Transactional Memory" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to tm+unsu...@isocpp.org.
> To post to this group, send email to t...@isocpp.org.
> Visit this group at http://groups.google.com/a/isocpp.org/group/tm/?hl=en.
>
>

Torvald Riegel

unread,
Mar 4, 2013, 10:18:08 AM3/4/13
to t...@isocpp.org
I see three issues regarding this:
1) We might have a custom copy constructor, so we need to run user code
and not just automatically generated code. (Even if there's no special
copy constructor for the object, we'd have the additional constraint
that no field must have a custom copy constructor either.)
2) We need to access some data without instrumentation.
3) We need to access some data with instrumentation because they might
have been written transactionally, unless we restrict TM implementations
(e.g., write-back wouldn't be allowed, nor would nonblocking
implementations).

Thus, we can't just disable instrumentation at some point, but instead
need to run a mix of instrumented and uninstrumented. That's why I
thought about an escape_memcpy() that would read transactionally and
write nontransactionally, for example. This isn't very pretty because
it would require programmers to copy data explicitly and at a low level,
but it would at least get the job done with clear semantics. Does
anyone have a suggestion how to provide this in a more convenient form?


Torvald

Michael L. Scott

unread,
Mar 4, 2013, 11:22:04 AM3/4/13
to t...@isocpp.org
On Mar 4, 2013, at 1:45 AM, Mark Moir wrote:

> On 3/4/13 7:34 PM, Jens Maurer wrote:
>>
>> So we need some kind of special instrumentation for the "copy exception
>> object" to turn off the TM bookkeeping, it seems.
>
> To my knowledge, this would not be supported in general by any HTM on the horizon. For example, Haswell's HTM allows an 8-bit "abort code" to survive the canceled hardware transaction, IIRC.

FWIW, it _does_ appear to be possible on Power 8, according to the recently-released draft update of the architecture manual.

But definitely not possible in general.

- Michael

Daniel Krügler

unread,
Mar 5, 2013, 3:01:59 PM3/5/13
to t...@isocpp.org, Mark Moir
2013/3/5 Niall Douglas <ndougl...@gmail.com>:
> I'm struggling to see how this is viable with Haswell's TSX implementation,
> insofar as I can currently grok it.
>
> How about instead, for now until we know more, you can't throw any exception
> inside a transaction which doesn't have std::is_trivial<exception
> type>::value as true?

The base class of all library exceptions, std::exception, is already
non-trivial. It seems like a bad idea to me to introduce just another
more fundamental exception type that user-code has to be aware of (In
general C++ allows any copyable type to be an exception type, but most
style guides forbid anything that is not at least derived some agreed
on base class, typically one that is derived from std::exception).

- Daniel

Mark Moir

unread,
Mar 5, 2013, 4:16:21 PM3/5/13
to Niall Douglas, t...@isocpp.org


On 3/6/13 4:40 AM, Niall Douglas wrote:
> On Monday, March 4, 2013 12:35:57 AM UTC-5, Dave Abrahams wrote:
>
>
> on Sun Mar 03 2013, Mark Moir <mark.moir-AT-oracle.com
> <http://mark.moir-AT-oracle.com>> wrote:
>
> > Does anyone else have any thoughts along these lines?
>
> My thought was that at the transaction boundary, TM bookkeeping
> would be
> turned off and the exception object could be copy-constructed into
> memory that's visible outside the transaction.
>
> I'm struggling to see how this is viable with Haswell's TSX
> implementation, insofar as I can currently grok it.

Agreed (see other followups).

>
> How about instead, for now until we know more, you can't throw any
> exception inside a transaction which doesn't have
> std::is_trivial<exception type>::value as true?
>
> This helps frame discussion by reducing to a subset of the problem, and
> lets everybody move on for now.

I don't think it solves the problem. Suppose I want to include code
that might throw a badalloc exception in a transaction. Such code would
break this rule. So does this mean we should not be allowed to do that?
Would the restriction be enforced statically, meaning that we simply
would not be able to do memory allocation within a transaction? Or
would the exception cause termination when/if it is thrown?

I'm OK with the idea that there might be significant optimisations for
trivial exceptions (or putting it differently, nontrivial ones might be
expensive due to falling back to software), and I'm OK with the idea
(reality) that it will not generally make sense to use all exceptions
that might be thrown within a transaction (e.g., one that includes
references to data allocated and initialised within the transaction).
In the latter case, we need to figure out some reasonable solution for
what to do with such exceptions. The discussion is mostly about
tradeoffs in how much rope to give programmers.

But I think imposing draconian constraints on what exceptions can be
thrown will severely limit usability.

Cheers

Mark












Mark Moir

unread,
Mar 5, 2013, 4:35:53 PM3/5/13
to Niall Douglas, t...@isocpp.org

Please note that we've explicitly avoided anything that *depends* on
hardware TM. HTM should be able to provide substantial optimisations
for important cases, but everything can be implemented in software.

Furthermore, given that HTM is now and for the foreseeable future not
"unbounded" (i.e., there will for a long time, maybe always, be
transactions that the HTM cannot support), all implementations will need
to be able to support the specified features in software only.

Cheers

Mark

On 3/6/13 10:30 AM, Niall Douglas wrote:
> The base class of all library exceptions, std::exception, is already
>
> non-trivial. It seems like a bad idea to me to introduce just another
> more fundamental exception type that user-code has to be aware of (In
> general C++ allows any copyable type to be an exception type, but most
> style guides forbid anything that is not at least derived some agreed
> on base class, typically one that is derived from std::exception).
>
> Sure. But practicalities are practicalities. If current proposed
> hardware support can't do it, it can't do it. Better therefore that we
> implement something which lets exception throws occur at all for this
> generation of hardware, and a try...catch wrapping the transaction can
> convert the trivial exception throw into a proper one. When the hardware
> improves, we can improve the spec.
>
> Do bear in mind I am referring here to a *draft* interim spec used for
> study. Haswell will get replaced before 2017 no doubt with something
> improved. I'm thinking very much in terms of a plugin for clang/LLVM
> with which I can familiarise myself with the technology, because I must
> admit I am ignorant of what it means and can do. I'd much prefer a
> working compiler sooner rather than later.
>
> I only suggest this idea because we could waste a huge amount of time
> arguing about exception handling instead of admitting no one knows and
> pressing on for now. We can fix things up later.
>
> Niall

Mark Moir

unread,
Mar 5, 2013, 5:01:54 PM3/5/13
to t...@isocpp.org

Hi Niall

I don't want to pretend that software TM is simple (it's not) or that
it's super efficient in all cases (it's not). But there has been a ton
of research on making it better in various ways, faster, more scalable,
better properties, integration with hardware TM, etc.

Compiler support is a very big deal (which of course is why we're here).
Programming to an STM library is horrible, both for programmers and
for performance (because a non-TM-aware compiler doesn't understand the
library's semantics, so many optimisation opportunities cannot be
exploited).

In any case, if you want the job of implementing the STM, I think you'll
need to work on your pitch :-). Otherwise, we can leave it to the Dave
Dices, Hans Boehms, and quite a few others who've done a lot of work in
this area over the years.

Finally, I hear what you're saying about choosing something simple in
order to make progress, and this is exactly what we're in the process of
doing. But it's important to do a good enough job so that when you
finally do get to play with it, you don't give up on day one because
it's too limiting (e.g., we received feedback on an early prototype of
our compiler, which did not support allocation within transactions, that
this was a dealbreaker for people who were willing to experiment with
it; we made it a top priority to support allocation in transactions).

Cheers

Mark





On 3/6/13 10:43 AM, Niall Douglas wrote:
> On Tuesday, March 5, 2013 4:35:53 PM UTC-5, Mark Moir wrote:
>
>
> Please note that we've explicitly avoided anything that *depends* on
> hardware TM. HTM should be able to provide substantial optimisations
> for important cases, but everything can be implemented in software.
>
> Furthermore, given that HTM is now and for the foreseeable future not
> "unbounded" (i.e., there will for a long time, maybe always, be
> transactions that the HTM cannot support), all implementations will
> need
> to be able to support the specified features in software only.
>
> Mmm. That's a *very* big ask. I've implemented transactions in software
> before, and they are very hard to get right and keep performance
> anything near reasonable. I found myself making many, many design errors
> and mistakes. Coding against them wasn't intuitive, either. I suppose
> with compiler support it might have been easier (this was in pre-clang
> days).
>
> That was almost certainly my naivity and inexperience - someone like
> Hans Boehm or David Dice would do far better than I for example. But I
> know my incompetence in software transactions, and I'd need to feel very
> comfortable with the hardware assist before I'd ever touch such a design
> again.
>
> That said, all power to this SG if you succeed. I'd still suggest
> keeping the problem absolutely as small as possible until we know more
> about future hardware. I know how easy it is to dig oneself into a hole
> you only realise six months later with this technology.
>
> Niall

Boehm, Hans

unread,
Mar 5, 2013, 5:54:51 PM3/5/13
to t...@isocpp.org, Mark Moir
Thinking out loud here:

Is Haswell the only architecture that prevents all nontransactional stores in a transaction? I'm trying to understand whether this is a well-motivated trend, or a version 1 accident. I doubt that anyone can answer that, but looking at (admittedly less influential) alternatives, might give us a hint. I know the AMD design didn't have this restriction.

We seem to already require software reexecution for exceptions in HTM-executed code in the case of nested transactions. If that's really a serious problem, the programmer can probably get around it (at serious modularity cost) by manually flattening the transactions. What makes this case a bit more troublesome is that the programmer may have no real way to tweak the code to avoid that overhead.

In that spirit, we should probably support a special case, e.g. throwing a small number of predefined exceptions, that can be handled in hardware. Should make_safe be a user specifiable function, with a library-defined lossy_make_safe, which is the default, and maps any bad_alloc to a fixed instance of bad_alloc, (and perhaps similarly for other standard section 18.7 and 18.8 exceptions), and everything else to a standard transaction_cancelled_exception?

If you use the default, you get no added overhead. The exception is temporarily represented as an abort code. If you specify your own we need STM reexecution, and you need to be very careful, but you can make things work.

Hans

Jens Maurer

unread,
Mar 5, 2013, 6:06:57 PM3/5/13
to t...@isocpp.org
On 03/04/2013 02:00 AM, Mark Moir wrote:
> If/when you do catch up on the email, you will see that we did not make
> progress on explicit cancel and have decided to go with a minimal
> proposal for now, in which the only way a transaction can get canceled
> is if it is annotated as [[cancel_on_escape]] and if an exception
> escapes from it.

Good.

> Thus, there is no special/new mechanism for communicating from the point
> at which we decide to cancel a transaction to the transaction being
> canceled.

Very good.

> People can use whatever convention they like, such as
> encoding information in exceptions, return values, etc. They will have
> to ensure it is safe to execute out to the transaction boundary even
> though the transaction will be canceled.

Given that people should already be trained to write exception-safe
code, this is probably just a minor missed optimization opportunity.

> I am not
> sure if your use of double underscore in __cancelled suggests that you
> mean it should be provided by the language, but this is not necessary:

Sorry, I just wanted to use a name that is unlikely to be used in
the actual transaction body. I do not want compiler support for this.

>> We can ignore that question for the time being, but then we should
>> also punt on support for advanced recovery schemes.
>
> Not sure what you mean here, can you elaborate?

If we make the minimal proposal even more minimal by not allowing
cancel-on-exception, i.e. a transaction always commits, then
the data export question does not arise. Alternatively, if we
decide that all exceptions escaping a transaction are translated
to the type TransactionCancelled, without any reference to the
original exception, then the data export question does not arise
either.

Some of last month's discussion seems to have focused on
recovery strategies for a failed transaction; since we can only
export a single bit from the cancelled transaction under the
scenarios described in the previous paragraph, the discussion
of recovery strategies would be moot. (Outside the transaction,
there is no way to know what went wrong.)

(These scenarios would probably not be super-helpful for
programmers, given that user feedback claimed that support
for memory allocations inside transactions was vital ---
and that might throw std::bad_alloc in the default case.)

Jens

Mark Moir

unread,
Mar 5, 2013, 6:24:14 PM3/5/13
to t...@isocpp.org
Thanks for the elaboration, I understand you better now.

> If we make the minimal proposal even more minimal by not allowing
> cancel-on-exception, i.e. a transaction always commits,

This is really not acceptable in my strong opinion. I'm attaching again
my email from October explaining why.

> then
> the data export question does not arise. Alternatively, if we
> decide that all exceptions escaping a transaction are translated
> to the type TransactionCancelled, without any reference to the
> original exception, then the data export question does not arise
> either.

That's a simple solution that people will hate. I'd be pretty
disappointed to see something this limited make it into any long-living
spec, but as an intermediate position to simplify our lives for now and
bring users out of the wordwork with some hatemail might be productive
:-). I still think it's worth trying to do a bit better for the next
version of the spec though.

Cheers

Mark

Jens Maurer

unread,
Mar 5, 2013, 6:47:52 PM3/5/13
to t...@isocpp.org
On 03/03/2013 11:22 PM, Jens Maurer wrote:
> Do we have an idea what can be reasonably implemented to export
> more than the minimum? Do we have a rough idea how the C++ syntax
> for such "cancel-survival-info" would look like?

Here are some thoughts:

- The following addresses the "cancel-on-exception" case; for the other
cases of the "minimalist proposal", there is no issue anyway.

- We need to answer the question how we can copy the exception object
that is about to vanish due to the transaction being cancelled in a way
so that the copy will remain visible after the cancellation.

- The fact that we're dealing with an exception object (as opposed to
any other object) is mostly irrelevant, except that we can specify that
a specially-annotated copy constructor should be called from that context.

- Cancellation via exception is assumed to be rare, thus cancelling the
hardware transaction and re-trying in software is feasible. This way,
we are not constrained by current hardware capabilities. HTM support
is always welcome to improve performance, but that's easier to argue
to the CPU designers when there is actual user experience.


Let's examine a few examples of object types and useful primitives
we'd need.

The setting is that a transaction is about to be cancelled, and
we want to copy an object such that the copy does not vanish
upon cancellation.

First of all, we need a way to allocate "surviving memory" (not necessarily
user-callable), or we will be limited to "small" types not exceeding a
fixed size.

For scalar types such as "int", all we need is a memcpy_from_tx_to_survival()
primitive. This also works for any trivially copyable type (see 3.9 [basic.types]).
Example types:

double

struct S1 {
int x;
double z;
char * p;
};


Then, there are types with single-ownership of dynamically allocated memory,
e.g. std::string or std::vector. These types have a user-defined copy
constructor which allocates memory. Obviously, this memory allocation
needs to allocate "surviving memory" for the purpose under discussion.
Since copying a std::string deep inside a transaction should not allocate
"surviving memory", we either need to have two different copy constructors,
or a global flag must be set prior to copying the exception object that
tells the memory allocator that, from now on, only "surviving memory"
is allocated. The latter approach doesn't solve the problem that
we need to use memcpy_from_tx_to_survival() to copy the actual character
contents of the std::string; std::memcpy() will not do the right thing.

So, we seem to require two copy constructors: one for regular operations
and one for "copy to survival" situations. This, obviously, needs
C++ language syntax to differentiate. Also, the "copy to survival"
constructor should not be autogenerated by the compiler unless all
members and bases are either trivially copyable or already have a
"copy to survival" constructor defined.

This would probably be enough infrastructure to use std::string,
std::vector, or std::exception (and compounds thereof) as exception
objects that survive a transaction cancellation.


Using a std::shared_ptr<> will never work. However, I'd envision that
the compiler calls the special "copy to survival" constructor for any
type that is not trivially copyable. That copy constructor would not
exist for a std::shared_ptr<>, and std::terminate would then be called.
(If the user is stupid enough to define a "copy to survival" constructor
for a std::shared_ptr<>, that's his fault.)


I would much prefer to have the "copy to survival" constructor defined
inside the class to which it applies instead of having separate
overloaded make_safe() functions: It needs intimate knowledge about
the inner details of the class to do the right thing.

Jens

Michael L. Scott

unread,
Mar 5, 2013, 8:06:42 PM3/5/13
to t...@isocpp.org, Mark Moir
On Mar 5, 2013, at 5:54 PM, Boehm, Hans wrote:

> Thinking out loud here:
>
> Is Haswell the only architecture that prevents all nontransactional
> stores in a transaction? I'm trying to understand whether this is a
> well-motivated trend, or a version 1 accident. I doubt that anyone
> can answer that, but looking at (admittedly less influential)
> alternatives, might give us a hint. I know the AMD design didn't have
> this restriction.

Actually, I _can_ answer that. I wouldn't stake my life on it, but I'm
pretty sure of the following.

non-transactional accesses permitted w/in transaction
read write
Sun/Oracle Rock Y C
AMD ASF Y I
Azul Vega 2 & 3 N N
IBM BG/Q Y I (very slow)
IBM zEC12 N C
IBM Power 8 Y I (possibly slow)
Intel Haswell N N

C = ordered at commit
I = ordered immediately (breaks isolation; non-serializable)

On BG/Q, non-transactional accesses require a trap to kernel mode and
a change of speculation context. On Power 8, they require temporary
suspension of speculation, which may not be cheap either. Implementation
details and timings for Power 8 have not yet be publicly revealed; my
"possibly slow" note is just a guess.

- Michael

Torvald Riegel

unread,
Mar 6, 2013, 7:14:29 AM3/6/13
to t...@isocpp.org
On Tue, 2013-03-05 at 13:30 -0800, Niall Douglas wrote:
> I'm thinking very much in terms of a plugin for clang/LLVM with which
> I can familiarise myself with the technology, because I must admit I
> am ignorant of what it means and can do. I'd much prefer a working
> compiler sooner rather than later.

There's this thing named GCC that supports most of the current draft
since release 4.7 already. And 4.8, which should appear soon, has more
improvements. It has a decent STM implementation, although it certainly
could be further optimized. It can also make use of Haswell's TM
support (although in the form of just a simple HTM fast path, no fancy
HyTM or such yet).


Torvald Riegel

unread,
Mar 6, 2013, 7:20:28 AM3/6/13
to t...@isocpp.org
On Wed, 2013-03-06 at 12:24 +1300, Mark Moir wrote:
> > If we make the minimal proposal even more minimal by not allowing
> > cancel-on-exception, i.e. a transaction always commits,
>
> This is really not acceptable in my strong opinion. I'm attaching again
> my email from October explaining why.

This email argues for the *ability* to cancel atomic transactions, using
failure atomicity as use case. Which I agree with. But note that this
doesn't automatically mean that we need to include cancel-on-escape in a
current spec.

It could be a useful example to motivate why we want the ability to
cancel, but it doesn't mean that an cancel-on-escape annotation on
transactions or the like is what we want. I think we can get
cancel-on-escape out once we really need to sell that we want to be able
to cancel.

Torvald

Michael L. Scott

unread,
Mar 6, 2013, 12:06:36 PM3/6/13
to t...@isocpp.org
On Mar 6, 2013, at 7:20 AM, Torvald Riegel wrote:

> On Wed, 2013-03-06 at 12:24 +1300, Mark Moir wrote:
>>> If we make the minimal proposal even more minimal by not allowing
>>> cancel-on-exception, i.e. a transaction always commits,
>>
>> This is really not acceptable in my strong opinion. I'm attaching again
>> my email from October explaining why.
>
> This email argues for the *ability* to cancel atomic transactions, using
> failure atomicity as use case. Which I agree with. But note that this
> doesn't automatically mean that we need to include cancel-on-escape in a
> current spec.

Please remember that there are some of us (myself among them) who think cancel-on-escape should be the _default_. I'm willing to accept a compromise in which it's an explicit option. I'm not comfortable getting rid of it; I think it's essential that it be part of our near-term proposal.

Mark Moir

unread,
Mar 6, 2013, 3:02:37 PM3/6/13
to t...@isocpp.org


On 3/7/13 1:20 AM, Torvald Riegel wrote:
> On Wed, 2013-03-06 at 12:24 +1300, Mark Moir wrote:
>>> If we make the minimal proposal even more minimal by not allowing
>>> cancel-on-exception, i.e. a transaction always commits,
>>
>> This is really not acceptable in my strong opinion. I'm attaching again
>> my email from October explaining why.
>
> This email argues for the *ability* to cancel atomic transactions, using
> failure atomicity as use case. Which I agree with. But note that this
> doesn't automatically mean that we need to include cancel-on-escape in a
> current spec.

If we know we need something, we should at least have some form of it.
It's true that we could continue to debate how it's expressed and that
could change in the future, but I see zero value to not having any way
to cancel a transaction in our next spec. That will allow/encourage
implementation approaches that can't support cancellation. That's
pretty much where the discussion in Portland began, with me trying to
convince the group that implementations that cannot cancel will not be
acceptable, regardless of how seductive their simplicity may be. I
think we've all agreed about that now, and the particular way it's
reflected in the proposed language features is important, but secondary
to making clear that implementations must have this ability.

Cheers

Mark

Jens Maurer

unread,
Mar 6, 2013, 4:12:02 PM3/6/13
to t...@isocpp.org

I wrote:
>>>> If we make the minimal proposal even more minimal by not allowing
>>>> cancel-on-exception, i.e. a transaction always commits,

On 03/06/2013 09:02 PM, Mark Moir wrote:
> If we know we need something, we should at least have some form of it.
> It's true that we could continue to debate how it's expressed and that
> could change in the future, but I see zero value to not having any way
> to cancel a transaction in our next spec.

Let me just say that I fully agree with Mark here.
My comment above was intended to explore one path which
I personally think is highly undesirable to pursue.

Sorry for sparking yet another "do we want cancellation" discussion.

Jens

Torvald Riegel

unread,
Mar 6, 2013, 6:12:47 PM3/6/13
to t...@isocpp.org
On Wed, 2013-03-06 at 00:47 +0100, Jens Maurer wrote:
> First of all, we need a way to allocate "surviving memory" (not necessarily
> user-callable), or we will be limited to "small" types not exceeding a
> fixed size.

We don't necessarily need to allocate memory for this. The escaping
data could be copied into a pre-allocated buffer.

> For scalar types such as "int", all we need is a memcpy_from_tx_to_survival()
> primitive.

This is the memcpy-based minimal library feature for this that we've
been discussing previously. I suppose you didn't attend the conf calls
during which this was discussed, but it's also in some of the email
threads IIRC.

The special memcpy approach is sufficient, but not convenient due to
being a pretty low-level facility. It would encapsulate the basic
operations we need (read transactionally, write nontransactionally).

If we have something like a custom copy constructor, I see two options
how to execute it:
1) Run instrumented code for the copy constructor, copy
nontransactionally to the target buffer.
2) Run uninstrumented code for the copy, read transactionally.

The first is better than the second IMO, because it allows the former to
also transactionally modify data if necessary. And the nontxnal writes
should in the majority TM implementations be immediately visible to the
transactional reads (which would be the default).

> This also works for any trivially copyable type (see 3.9 [basic.types]).
> Example types:
>
> double
>
> struct S1 {
> int x;
> double z;
> char * p;
> };
>
>
> Then, there are types with single-ownership of dynamically allocated memory,
> e.g. std::string or std::vector. These types have a user-defined copy
> constructor which allocates memory. Obviously, this memory allocation
> needs to allocate "surviving memory" for the purpose under discussion.

But this doesn't need to be a malloc/new/.. call. This could also just
target an existing memory buffer (although that one might argue that
this requires some kind of allocation in this buffer too).

> Since copying a std::string deep inside a transaction should not allocate
> "surviving memory", we either need to have two different copy constructors,
> or a global flag must be set prior to copying the exception object that
> tells the memory allocator that, from now on, only "surviving memory"
> is allocated. The latter approach doesn't solve the problem that
> we need to use memcpy_from_tx_to_survival() to copy the actual character
> contents of the std::string; std::memcpy() will not do the right thing.

I've been thinking about the allocation approach too, and I don't think
special memcpy is always required. If we allocate (or mark) all memory
regions for which writes targeting them should escape the transaction,
then there are several ways how the TM implementation can do that:
* The TM can copy only this data out on abort, or it can just not roll
it back.
* The TM can intercept writes to such regions and don't include them in
undo logs or write them through to memory immediately. This can be done
without slowing down normal writes (e.g., in a blocking impl, you can
pre-lock the regions and then do the checking in the slow path of txnal
writes).

When copying an object, there would be allocations for all targeted
data, so enforcing a special allocation method (provided by the TM impl)
would be sufficient, and we don't need the memcpy in this case. This
avoids having another code path for any copy constructor.

I'm not sure how we can best enforce a special allocation *just* for the
copy's target objects though:
* The global flag you mention won't work in general because copy
constructors are allowed to allocate other data too; if we make this
other data escape too, then this could lead to memory leaks or whatever.
* The compiler can't always figure out for user-provided copy
constructors which data needs to escape and which not; this would need
points-to analysis to always work, which is not the case. (Note that
this also means that the compiler can't always figure out where to use a
normal memcpy and where to use the escaping memcpy.)
* There might be allocations in a user-provided copy constructor that
the compiler can't detect as allocations.
* I'm not aware of a way to "inject" a custom allocator into a type so
that this would then apply to all fields of the type too. We could
perhaps clone the type and replace any contained types too, but this
sounds like lots of work and I'm not sure whether it would always work
(e.g., what do we do with custom allocators?).

For *just* default copy constructors, a per-thread flag would work,
which could even be hidded in the TM impl because all allocations, even
in copy constructors or exception handling, go through the TM runtime
library. I haven't thought about how to enforce that this just happens
for default constructors, but my gut feeling is that this should be
implementable (in the compiler!).

For custom copy constructors, perhaps we need to require programmers to
use the special escaping memory allocator. I haven't thought too much
about how to let them express this. Explicit allocation could be an
option. We could perhaps also provide a function that, in txnal
context, either calls the default copy constructor or terminates (i.e.,
to ensure the previous requirement for just running default copy ctrs
as-is). Nonetheless, once we want to use non-trivially-copyable objects
from the stdlib, we'd probably need to at least review their copy
constructors and perhaps also annotate them; this might be a large
implementation effort, and would perhaps need annotations.

> So, we seem to require two copy constructors: one for regular operations
> and one for "copy to survival" situations. This, obviously, needs
> C++ language syntax to differentiate. Also, the "copy to survival"
> constructor should not be autogenerated by the compiler unless all
> members and bases are either trivially copyable or already have a
> "copy to survival" constructor defined.

Having another annotation for an escape-safe copy ctr might be an
option, yes. But perhaps there's something better.

> Using a std::shared_ptr<> will never work. However, I'd envision that
> the compiler calls the special "copy to survival" constructor for any
> type that is not trivially copyable. That copy constructor would not
> exist for a std::shared_ptr<>, and std::terminate would then be called.
> (If the user is stupid enough to define a "copy to survival" constructor
> for a std::shared_ptr<>, that's his fault.)

There might be scenarios in which it might even make sense for
shared_ptr (e.g., roll back one ref but keep a copy; so, overall, keep
the obj), but I haven't thought about this in detail.

> I would much prefer to have the "copy to survival" constructor defined
> inside the class to which it applies instead of having separate
> overloaded make_safe() functions: It needs intimate knowledge about
> the inner details of the class to do the right thing.

Agreed, yet OTOH, having it external makes it perhaps easier to add.

In general, I'd like to see the escaping functionally eventually become
a proposal for a generic feature that can be used together with
cancellation but also for other use cases that require letting data
escape transactional isolation and atomicity. We'd need some more bits
for the latter, but I think it would be worthwhile.

Torvald

Jens Maurer

unread,
Mar 6, 2013, 6:48:44 PM3/6/13
to t...@isocpp.org
On 03/07/2013 12:12 AM, Torvald Riegel wrote:
> On Wed, 2013-03-06 at 00:47 +0100, Jens Maurer wrote:
>> First of all, we need a way to allocate "surviving memory" (not necessarily
>> user-callable), or we will be limited to "small" types not exceeding a
>> fixed size.
>
> We don't necessarily need to allocate memory for this. The escaping
> data could be copied into a pre-allocated buffer.

And what is the size of that buffer? More precisely, how is that
size determined?

>> For scalar types such as "int", all we need is a memcpy_from_tx_to_survival()
>> primitive.
>
> This is the memcpy-based minimal library feature for this that we've
> been discussing previously. I suppose you didn't attend the conf calls
> during which this was discussed,

Right.

> but it's also in some of the email
> threads IIRC.

Yes.

> The special memcpy approach is sufficient, but not convenient due to
> being a pretty low-level facility. It would encapsulate the basic
> operations we need (read transactionally, write nontransactionally).

That's fine, it's mostly an exposition device to explain the
semantics needed.

> If we have something like a custom copy constructor, I see two options
> how to execute it:

I presume, with "custom copy constructor" you mean "a special
copy constructor for tx exception copies, syntactically different from
the one already existent in C++".

> 1) Run instrumented code for the copy constructor, copy
> nontransactionally to the target buffer.
> 2) Run uninstrumented code for the copy, read transactionally.
>
> The first is better than the second IMO, because it allows the former to
> also transactionally modify data if necessary.

Yes. When performing the copy, we're conceptually still inside the
transaction boundaries, so #1 seems like the natural choice.

> And the nontxnal writes
> should in the majority TM implementations be immediately visible to the
> transactional reads (which would be the default).

I'm not sure whether that's important here.

>> This also works for any trivially copyable type (see 3.9 [basic.types]).
>> Example types:
>>
>> double
>>
>> struct S1 {
>> int x;
>> double z;
>> char * p;
>> };
>>
>>
>> Then, there are types with single-ownership of dynamically allocated memory,
>> e.g. std::string or std::vector. These types have a user-defined copy
>> constructor which allocates memory. Obviously, this memory allocation
>> needs to allocate "surviving memory" for the purpose under discussion.
>
> But this doesn't need to be a malloc/new/.. call. This could also just
> target an existing memory buffer (although that one might argue that
> this requires some kind of allocation in this buffer too).

It's conceivable to have an malloc_surviving() call, or a special
overloaded operator new for that purpose. Where these get their memory
from, seems to be an implementation detail.

> I'm not sure how we can best enforce a special allocation *just* for the
> copy's target objects though:
> * The global flag you mention won't work in general because copy
> constructors are allowed to allocate other data too; if we make this
> other data escape too, then this could lead to memory leaks or whatever.

Agreed.

> * The compiler can't always figure out for user-provided copy
> constructors which data needs to escape and which not; this would need
> points-to analysis to always work, which is not the case. (Note that
> this also means that the compiler can't always figure out where to use a
> normal memcpy and where to use the escaping memcpy.)

Agreed. So we need different user-written syntax for both to help
the compiler out.

> For *just* default copy constructors, a per-thread flag would work,
> which could even be hidded in the TM impl because all allocations, even
> in copy constructors or exception handling, go through the TM runtime
> library. I haven't thought about how to enforce that this just happens
> for default constructors, but my gut feeling is that this should be
> implementable (in the compiler!).

If you're targeting defaulted copy constructors, I think you can just
have the compiler generate a second version of the copy constructor
which then calls the special memcpy() functions. No global flag
required at all.

> For custom copy constructors, perhaps we need to require programmers to
> use the special escaping memory allocator. I haven't thought too much
> about how to let them express this. Explicit allocation could be an
> option. We could perhaps also provide a function that, in txnal
> context, either calls the default copy constructor or terminates (i.e.,
> to ensure the previous requirement for just running default copy ctrs
> as-is). Nonetheless, once we want to use non-trivially-copyable objects
> from the stdlib, we'd probably need to at least review their copy
> constructors and perhaps also annotate them; this might be a large
> implementation effort, and would perhaps need annotations.

I'd suggest to leave existing copy constructors alone and have a second,
syntactically different copy constructor for the exception escape
copy:
struct S {
S(const S&) { ... } // normal copy constructor
__transaction S(const S&) { ... } // special copy constructor
};

The latter can then call the special memory allocation and memcpy functions
as necessary.

> Having another annotation for an escape-safe copy ctr might be an
> option, yes. But perhaps there's something better.

I don't think so, unless we want to severely limit the kinds of
expressions allowable in a copy constructor.

>> I would much prefer to have the "copy to survival" constructor defined
>> inside the class to which it applies instead of having separate
>> overloaded make_safe() functions: It needs intimate knowledge about
>> the inner details of the class to do the right thing.
>
> Agreed, yet OTOH, having it external makes it perhaps easier to add.

The "copy to survival" constructor will need to access private members
of the class: That's hard for an external function added ex-post.

> In general, I'd like to see the escaping functionally eventually become
> a proposal for a generic feature that can be used together with
> cancellation but also for other use cases that require letting data
> escape transactional isolation and atomicity.

Yes, that's a natural fall-out.

Jens

Mark Moir

unread,
Mar 6, 2013, 10:39:39 PM3/6/13
to t...@isocpp.org

Hi Jens and Torvald

I feel there is too much complexity here for a case that is probably
quite rare and makes sense even more rarely :-). Much of the complexity
stems from a desire to support throwing arbitrary size exceptions from
canceled transactions.

What if we were to choose some fixed size (say 1K for argument's sake),
and choose not to allow more than this much information to escape from a
canceled transaction? That way, the special memory could be
preallocated and there would be no need to allocate it during execution
of the transaction.

If we made everything work nicely for a predefined (by the spec and/or
implementation) set of "small", simple exceptions, and give the
programmer enough rope to be able to identify a special memory area,
both within the transaction and after it has been canceled, we could
leave it to the programmer to decide what gets copied into this special
memory area, and how it is interpreted after the transaction is
canceled. The language would treat it specially only in that it does
not get rolled back when the transaction is canceled.

I think this seems to provide a good balance of working nicely in the
most common cases, while allowing flexibility for more uncommon cases,
up to a point, but not to the point of supporting really ridiculous use
cases.

What do you think?

Cheers

Mark

Torvald Riegel

unread,
Mar 7, 2013, 5:12:37 AM3/7/13
to t...@isocpp.org
On Thu, 2013-03-07 at 00:48 +0100, Jens Maurer wrote:
> On 03/07/2013 12:12 AM, Torvald Riegel wrote:
> > On Wed, 2013-03-06 at 00:47 +0100, Jens Maurer wrote:
> >> First of all, we need a way to allocate "surviving memory" (not necessarily
> >> user-callable), or we will be limited to "small" types not exceeding a
> >> fixed size.
> >
> > We don't necessarily need to allocate memory for this. The escaping
> > data could be copied into a pre-allocated buffer.
>
> And what is the size of that buffer? More precisely, how is that
> size determined?

That's something the program can care about. One could argue that this
needs to be dynamically sized to be composable, but I think this point
isn't essential to our discussion here. There should be enough cases
where there's no need to allocate dynamically.

> >> For scalar types such as "int", all we need is a memcpy_from_tx_to_survival()
> >> primitive.
> >
> > This is the memcpy-based minimal library feature for this that we've
> > been discussing previously. I suppose you didn't attend the conf calls
> > during which this was discussed,
>
> Right.
>
> > but it's also in some of the email
> > threads IIRC.
>
> Yes.
>
> > The special memcpy approach is sufficient, but not convenient due to
> > being a pretty low-level facility. It would encapsulate the basic
> > operations we need (read transactionally, write nontransactionally).
>
> That's fine, it's mostly an exposition device to explain the
> semantics needed.

I guess it would be useful to expose it to programmers and not just use
it to model the semantics, especially when we want the escape mechanism
to become a more general feature.

> > If we have something like a custom copy constructor, I see two options
> > how to execute it:
>
> I presume, with "custom copy constructor" you mean "a special
> copy constructor for tx exception copies, syntactically different from
> the one already existent in C++".

No, I mean the case where a programmer writes a custom copy constructor
for a certain class, so that there's no default copy constructor; what's
the proper C++ naming for this case? The key here is that we need to
involve custom programmer-provided code for the copying.

> > 1) Run instrumented code for the copy constructor, copy
> > nontransactionally to the target buffer.
> > 2) Run uninstrumented code for the copy, read transactionally.
> >
> > The first is better than the second IMO, because it allows the former to
> > also transactionally modify data if necessary.
>
> Yes. When performing the copy, we're conceptually still inside the
> transaction boundaries, so #1 seems like the natural choice.
>
> > And the nontxnal writes
> > should in the majority TM implementations be immediately visible to the
> > transactional reads (which would be the default).
>
> I'm not sure whether that's important here.

If we want to be able to implement it, I think it's relevant :) For
example, if the compiler can't figure out at compile time whether a
certain access is targeting escaping or non-escaping data, then it helps
if you can just do a transactional read and don't require the runtime to
handle this case specially.

> >> This also works for any trivially copyable type (see 3.9 [basic.types]).
> >> Example types:
> >>
> >> double
> >>
> >> struct S1 {
> >> int x;
> >> double z;
> >> char * p;
> >> };
> >>
> >>
> >> Then, there are types with single-ownership of dynamically allocated memory,
> >> e.g. std::string or std::vector. These types have a user-defined copy
> >> constructor which allocates memory. Obviously, this memory allocation
> >> needs to allocate "surviving memory" for the purpose under discussion.
> >
> > But this doesn't need to be a malloc/new/.. call. This could also just
> > target an existing memory buffer (although that one might argue that
> > this requires some kind of allocation in this buffer too).
>
> It's conceivable to have an malloc_surviving() call, or a special
> overloaded operator new for that purpose. Where these get their memory
> from, seems to be an implementation detail.

Right; what I wanted to point out is that we can't necessarily detect a
logical allocation in a custom copy constructor (ie, where the program
logically marks data that should escape).

> > I'm not sure how we can best enforce a special allocation *just* for the
> > copy's target objects though:
> > * The global flag you mention won't work in general because copy
> > constructors are allowed to allocate other data too; if we make this
> > other data escape too, then this could lead to memory leaks or whatever.
>
> Agreed.
>
> > * The compiler can't always figure out for user-provided copy
> > constructors which data needs to escape and which not; this would need
> > points-to analysis to always work, which is not the case. (Note that
> > this also means that the compiler can't always figure out where to use a
> > normal memcpy and where to use the escaping memcpy.)
>
> Agreed. So we need different user-written syntax for both to help
> the compiler out.
>
> > For *just* default copy constructors, a per-thread flag would work,
> > which could even be hidded in the TM impl because all allocations, even
> > in copy constructors or exception handling, go through the TM runtime
> > library. I haven't thought about how to enforce that this just happens
> > for default constructors, but my gut feeling is that this should be
> > implementable (in the compiler!).
>
> If you're targeting defaulted copy constructors, I think you can just
> have the compiler generate a second version of the copy constructor
> which then calls the special memcpy() functions. No global flag
> required at all.

The global flag is easier for implementations if you put this into the
TM runtime lib. We're executing instrumented code when we have default
copy constructors generated by the compiler, so this flag needs to just
affect transactional versions of allocations. The compiler could also
generate a separate version for copy constructors, but I don't think
this is easier; it allows for certain optimizations though, for example
on HTMs, which then could use nonspeculative ops to copy to the escaping
memory region.

>
> > For custom copy constructors, perhaps we need to require programmers to
> > use the special escaping memory allocator. I haven't thought too much
> > about how to let them express this. Explicit allocation could be an
> > option. We could perhaps also provide a function that, in txnal
> > context, either calls the default copy constructor or terminates (i.e.,
> > to ensure the previous requirement for just running default copy ctrs
> > as-is). Nonetheless, once we want to use non-trivially-copyable objects
> > from the stdlib, we'd probably need to at least review their copy
> > constructors and perhaps also annotate them; this might be a large
> > implementation effort, and would perhaps need annotations.
>
> I'd suggest to leave existing copy constructors alone and have a second,
> syntactically different copy constructor for the exception escape
> copy:
> struct S {
> S(const S&) { ... } // normal copy constructor
> __transaction S(const S&) { ... } // special copy constructor
> };
>
> The latter can then call the special memory allocation and memcpy functions
> as necessary.

Because they won't need to call the special memcpy functions, I believe
it's easier to be able to reuse existing copy constructors; for most of
them, it should be sufficient to review the code. ISTM that this would
be better than duplicating lots of existing copy constructors.

It would be great if the compiler can figure out that a custom copy
constructor is "safe" -- then we wouldn't have to do any special
annotations. But I haven't thought in detail about this.

> > Having another annotation for an escape-safe copy ctr might be an
> > option, yes. But perhaps there's something better.
>
> I don't think so, unless we want to severely limit the kinds of
> expressions allowable in a copy constructor.

Can you elaborate why you think that there won't be a way? Or do you
just don't see a way currently?

> >> I would much prefer to have the "copy to survival" constructor defined
> >> inside the class to which it applies instead of having separate
> >> overloaded make_safe() functions: It needs intimate knowledge about
> >> the inner details of the class to do the right thing.
> >
> > Agreed, yet OTOH, having it external makes it perhaps easier to add.
>
> The "copy to survival" constructor will need to access private members
> of the class: That's hard for an external function added ex-post.
>
> > In general, I'd like to see the escaping functionally eventually become
> > a proposal for a generic feature that can be used together with
> > cancellation but also for other use cases that require letting data
> > escape transactional isolation and atomicity.
>
> Yes, that's a natural fall-out.

Well, it requires additional work, so I guess it doesn't just fall
out :)
For example, we don't have a single "root" anymore (like the exception
object, where we can implicitly forward the pointer to it and thus hand
over all escaping memory). Furthermore, there are different kinds of
data escape:

* Escape on explicit abort / cancellation: Like for exceptions, we want
the memory to not be rolled back on an explicit abort, but want it to be
rolled back on another kind of abort (e.g., if there's a data conflict
while running the copy constructor).

* Escape at some time before abort or commit: This could be used for
things like logging, giving hints to custom recovery mechanisms (e.g.,
"right now I'm about to do X").

These have somewhat different atomicity requirements.


Torvald


Niall Douglas

unread,
Mar 7, 2013, 11:43:16 AM3/7/13
to t...@isocpp.org, Mark Moir
You almost certainly already know this Hans, but I don't think it's a version 1 accident, rather it's carefully thought through. The way I read it with Haswell Intel intends you to nest them, so basically:

If(op1 succeeds)
{
  if(op2 succeeds)
    commit success of op1 and op2;
  else
    commit success of op1 and failure of op2;
}
else
  commit failure of op1 and op2;

All of which is fair enough, and you can implement full fat C++ with that approach. by basically implementing Haswell STM as a form of internal C++ exception. However the nesting level is a maximum of seven, which is a huge limitation. And that combined with the implied granularity of the above approach makes Haswell transactions very much a micro-optimisation approach, and not really memory transactions in the "traditional" sense.

Niall

Niall Douglas

unread,
Mar 7, 2013, 11:59:45 AM3/7/13
to t...@isocpp.org, tri...@redhat.com
You could have done without the snarky tone. However, if I was unclear as to my intent, then I do apologise: I'm not interested in software transactions in their traditional formulation, mainly because you can metaprogram the C++ compiler with a "good enough" transaction implementation, and that's the style of code I've written for the last decade.

What I *am* interested in is what hardware acceleration can give me. And that, unfortunately, really means whatever hardware acceleration Intel and ARM provide.

Right now, Haswell's TM doesn't appear to me to have much to do with memory transactions: rather, it's primarily yet another way to avoid pipeline stalls by basically allowing the CPU to speculatively execute code past a CAS before that CAS has actually been acquired. While they were at it, they opened up the same machinery to a more generic use, so you can manually program the logic to determine the CAS value instead of it being a memory location.

I'll give a quick example: right now in my after work hours spare time I'm writing a SIMD batch SHA-256 implementation. It basically takes a list of memory regions to be SHAed, and feeds them through a four-SHA256 implementation written in SSE2. SHA-256 works in 64 byte chunks, so you iterate through as many 64 byte chunks per SHA range as you can, and so therefore there is necessarily logic between each SHA round to work out which of the SIMD streams need filling with which data. This is necessarily full of memory dependencies and pipeline stalls.

Using Haswell's TM I could let the CPU speculatively execute many of those code paths in that intermediate logic using its otherwise unused execute units, having the CPU "choose" the path actually taken. That would greatly improve its performance.

Thing is, Haswell's TM isn't really memory transactions in the traditional sense. It's a micro-optimisation tool really. I'm too ignorant to say yet how much compiler support - outside the inevitable intrinsics - is actually needed here with such a lightweight implementation.

Niall

Torvald Riegel

unread,
Mar 7, 2013, 12:46:32 PM3/7/13
to Niall Douglas, t...@isocpp.org
On Thu, 2013-03-07 at 08:59 -0800, Niall Douglas wrote:
> Thing is, Haswell's TM isn't really memory transactions in the
> traditional sense. It's a micro-optimisation tool really. I'm too
> ignorant to say yet how much compiler support - outside the inevitable
> intrinsics - is actually needed here with such a lightweight
> implementation.

Sorry, I can't follow you. RTM in Haswell provides you with
transactions with strong isolation; that looks a lot like "traditional"
TM to me. Regarding compiler support: The current Intel/Red Hat ABI for
TM runtime libraries is sufficient to have pure library implemenations
of transactions on top of RTM.
Either way, I think SG5 isn't the right forum to discuss specific HW
vendor TM implementations, unless they show general trends that we need
to consider.

Torvald

Torvald Riegel

unread,
Mar 7, 2013, 1:00:56 PM3/7/13
to t...@isocpp.org
On Thu, 2013-03-07 at 16:39 +1300, Mark Moir wrote:
> Hi Jens and Torvald
>
> I feel there is too much complexity here for a case that is probably
> quite rare and makes sense even more rarely :-). Much of the complexity
> stems from a desire to support throwing arbitrary size exceptions from
> canceled transactions.

No, that's not the key problem here. Instead, it's about finding out
which regions of memory should have their writes escape the transaction.
Getting this information from allocations seems to be easier, whereas
you can't easily find out which memory stores are supposed to escape.
(You could perhaps for compiler-generated default constructors, but the
allocation-based method seems much easier on implementations.)

> What if we were to choose some fixed size (say 1K for argument's sake),
> and choose not to allow more than this much information to escape from a
> canceled transaction? That way, the special memory could be
> preallocated and there would be no need to allocate it during execution
> of the transaction.
>
> If we made everything work nicely for a predefined (by the spec and/or
> implementation) set of "small", simple exceptions, and give the
> programmer enough rope to be able to identify a special memory area,
> both within the transaction and after it has been canceled, we could
> leave it to the programmer to decide what gets copied into this special
> memory area, and how it is interpreted after the transaction is
> canceled. The language would treat it specially only in that it does
> not get rolled back when the transaction is canceled.

That's the manual memcpy approach. And there's nothing wrong with that,
but it has shortcomings, and it's not convenient. You would have to
rewrite copy constructors. In contrast, if you base it on the
allocations, you just need a few constraints on copy constructors and
the like. For example, iff all allocated memory is supposed to escape
(and the target obj of the copy constructor is allocated too), then you
can run copy constructors unmodified (provided TM runtime lib support).
That should be much easier to check for libraries and other codes -- at
least for programmers, but perhaps the compiler can also analyze some
cases.


Torvald

Mark Moir

unread,
Mar 7, 2013, 5:56:53 PM3/7/13
to t...@isocpp.org


On 3/8/13 7:00 AM, Torvald Riegel wrote:
> On Thu, 2013-03-07 at 16:39 +1300, Mark Moir wrote:
>> Hi Jens and Torvald
>>
>> I feel there is too much complexity here for a case that is probably
>> quite rare and makes sense even more rarely :-). Much of the complexity
>> stems from a desire to support throwing arbitrary size exceptions from
>> canceled transactions.
>
> No, that's not the key problem here. Instead, it's about finding out
> which regions of memory should have their writes escape the transaction.
> Getting this information from allocations seems to be easier, whereas
> you can't easily find out which memory stores are supposed to escape.
> (You could perhaps for compiler-generated default constructors, but the
> allocation-based method seems much easier on implementations.)

The intermediate point I am suggesting still considers (a) special
area(s) of memory to be "survivable", so we can simply write to it as
usual within the transaction, and the rollback infrastructure will know
not to roll back changes to that special memory (or alternatively, it
will know that it does have to do the copyback from the writeset to this
memory even though the transaction is being canceled).

What is different is that I am suggesting a fixed size area whose size
is known in advance, so there will be no need for dynamically allocating
such special memory areas, tracking multiple ones, etc. I think this
simplifies the implementation (not that I am an expert here, of course).

I do agree with your comments below that this will put additional burden
on programmers who want to deal with nontrivial exceptions, especially
those that can't fit into the fixed size and therefore necessarily must
be transformed into something else. But I'm OK with that given that I
think it will hardly ever make sense, so I don't think we should
complicate the lives of specifiers and implementors to enable it.

Cheers

Mark

Jens Maurer

unread,
Mar 8, 2013, 4:45:55 AM3/8/13
to t...@isocpp.org
On 03/07/2013 04:39 AM, Mark Moir wrote:
>
> Hi Jens and Torvald
>
> I feel there is too much complexity here for a case that is probably
> quite rare and makes sense even more rarely :-). Much of the complexity
> stems from a desire to support throwing arbitrary size exceptions from
> canceled transactions.

I think that's not the main point, as shown by Torvald already.

Here's a specific example:


struct E {

E(const E& other) : s1(other.s1)
{
s2 = other.s2;
std::string s4 = other.s3;
// do something with s4
s3 = s4;
}

std::string s1;
my_string s2;
std::string s3;
};


Let's assume an exception of type E wants to escape a transaction.

- The copy of s1 will allocate memory using "operator new".
How does the compiler know it needs to allocating surviving dynamic
memory for that? And does that memory come from the special fixed-
size pool Mark is talking about? If so, how does the destructor of
s1, which will eventually call "operator delete" in a non-transactional
context for the escaped copy of E, know to free the memory from the
fixed-size pool instead of the general "malloc" area?

- Same question for s2, but this time it's not visible in the
mem-initializer-list what's going on. How does the copy constructor
of my_string know what to do?

- How does the compiler know that s4 and the memory allocated for
it does not escape?

- How does the compiler know that the copy to s3 is an escaping copy?

I believe in the general case, the answer is "it can't know".
Admittedly, all code paths taken above are (potentially)) instrumented
code paths, but that doesn't seem to help a lot.


We can certainly establish rules that address simpler cases than the
above, e.g.

struct E {

E(const E& other) : s1(other.s1), s2(other.s2), s3(other.s3)
{ }

std::string s1;
my_string s2;
std::string s3;
};

is probably easier to handle automatically.


What I would not like to have is some automatic rules that silently
fail to do the right thing for some exception classes. Failing loudly
at compile-time is best ("I don't know what to do here, please help me");
failing at runtime (std::terminate()) is at least a deterministic reaction.

Jens

Torvald Riegel

unread,
Mar 8, 2013, 7:20:16 AM3/8/13
to t...@isocpp.org
On Fri, 2013-03-08 at 11:56 +1300, Mark Moir wrote:
>
> On 3/8/13 7:00 AM, Torvald Riegel wrote:
> > On Thu, 2013-03-07 at 16:39 +1300, Mark Moir wrote:
> >> Hi Jens and Torvald
> >>
> >> I feel there is too much complexity here for a case that is probably
> >> quite rare and makes sense even more rarely :-). Much of the complexity
> >> stems from a desire to support throwing arbitrary size exceptions from
> >> canceled transactions.
> >
> > No, that's not the key problem here. Instead, it's about finding out
> > which regions of memory should have their writes escape the transaction.
> > Getting this information from allocations seems to be easier, whereas
> > you can't easily find out which memory stores are supposed to escape.
> > (You could perhaps for compiler-generated default constructors, but the
> > allocation-based method seems much easier on implementations.)
>
> The intermediate point I am suggesting still considers (a) special
> area(s) of memory to be "survivable", so we can simply write to it as
> usual within the transaction, and the rollback infrastructure will know
> not to roll back changes to that special memory (or alternatively, it
> will know that it does have to do the copyback from the writeset to this
> memory even though the transaction is being canceled).
>
> What is different is that I am suggesting a fixed size area whose size
> is known in advance, so there will be no need for dynamically allocating
> such special memory areas, tracking multiple ones, etc. I think this
> simplifies the implementation (not that I am an expert here, of course).

This is not at all about whether we have a fixed size at all or not. It
is about how we get the information from programmers which stores should
escape or not. Both the fixed size area you have in mind and anything
allocated dynamically have *some* abstract regions of memory that
escape.

What differs is that if you have a predefined area, then programmers
need to explicitly copy into the abstract regions of memory (i.e., the
predefined area). This either requires them to
1) get a pointer to the predefined area and use stock memcpy(), or
2) use a custom allocator for their objects that allocates memory from
the predefined area, or
3) use a special escaping_memcpy() that does some of the above.

You seem to have (1) or (3) in mind, both of which require a customized
copy constructor. What we are saying is that iff we can say that all
memory allocated in a copy constructor is supposed to escape, then
there's no need to modify copy constructors because we can ensure (2)
under the hood (ie, hook into the txnal versions of the default
allocator) and then use the normal copy constructor and normal
instrumentation. In the exception case, this hook could be a
per-transaction flag that's set before copying an exception object that
supposed to be thrown on cancellation.

> I do agree with your comments below that this will put additional burden
> on programmers who want to deal with nontrivial exceptions, especially
> those that can't fit into the fixed size and therefore necessarily must
> be transformed into something else.

This is not about the fixed size -- please roll back this thought :)

> But I'm OK with that given that I
> think it will hardly ever make sense, so I don't think we should
> complicate the lives of specifiers and implementors to enable it.

It's not exceeding the fixed size that we're worried about, but
non-default copy constructors as shown in the Jens' example.

I believe that the rule "all memory allocated in a copy constructor is
supposed to escape" is a good base and should be relatively easy to
understand. IMO, it's an easier rule than whatever else we discussed so
far regarding the escape mechanism for cancel-and-throw, or regarding
which exceptions would be safe. For example, everything trivially
copyable is obviously safe under this rule. I suspect many standard lib
non-default copy constructors should also be safe.

Does this clarify the approach for you?

Torvald

Torvald Riegel

unread,
Mar 8, 2013, 7:33:20 AM3/8/13
to t...@isocpp.org
If our requirement for copy constructors is that exactly all the memory
that is allocated in the copy constructor escapes, then s4 should be
fine because we release the memory before the copy constructor has
finished. Implementing this should be straight-forward.

> - How does the compiler know that the copy to s3 is an escaping copy?
>
> I believe in the general case, the answer is "it can't know".
> Admittedly, all code paths taken above are (potentially)) instrumented
> code paths, but that doesn't seem to help a lot.

I agree. But if we can make the assumption that exactly all the memory
that is allocated in the copy constructor is also supposed to escape,
then we don't have these issues, and we can use the normal instrumented
code if we want.

This assumption holds for default copy constructors. For
programmer-provided custom copy constructors, we would need to require
this. We could either pin this requirement to the escaping action in
the transaction (e.g., require that for cancel-and-throw exceptions), or
require annotations on custom copy constructors. Thoughts?

Of course, issues like dangling pointers to things that will be rolled
back by the transaction won't go away. But they never will -- programs
will always be able to violate invariants when they let some bits escape
yet cancel the transaction.

>
> We can certainly establish rules that address simpler cases than the
> above, e.g.
>
> struct E {
>
> E(const E& other) : s1(other.s1), s2(other.s2), s3(other.s3)
> { }
>
> std::string s1;
> my_string s2;
> std::string s3;
> };
>
> is probably easier to handle automatically.
>
>
> What I would not like to have is some automatic rules that silently
> fail to do the right thing for some exception classes. Failing loudly
> at compile-time is best ("I don't know what to do here, please help me");
> failing at runtime (std::terminate()) is at least a deterministic reaction.

I'd also like to avoid this being error-prone, but for me, the highest
priority is to offer something that is more convenient than the explicit
memcpy() approach. For example, if we are confident that compiler
implementations can warn whenever a program uses an unsafe exception
class, then this would be sufficient as far as I'm concerned.

Torvald

Mark Moir

unread,
Mar 10, 2013, 7:48:32 PM3/10/13
to t...@isocpp.org

Hi Jens and Torvald

You are both starting with an assumption that a copy constructor will be
used for general exceptions. With that assumption, the concerns you
raise seem valid.

I am assuming something more primitive, which I think Torvald has
clarified is what he refers to as "the memcpy approach". But I am *not*
assuming that the entire exception needs to be able to escape, and I am
suggesting that we don't need to give the programmer the convenience of
general copy constructors for this purpose.

I haven't thought through the details of questions Torvald raises much,
but let's assume something like this for argument's sake:

- I can call

void* std::give_me_special_block()

to receive a pointer to my special, fixed-size block of memory that will
not be rolled back upon cancellation.

- when I call it again after the transaction is canceled, I get the same
pointer again, so I know where to find the stuff I stashed before the
transaction is canceled.

The original exception is destroyed as if it were caught and ignored in
a catch block inside the canceled transaction.

The choice of what the transaction writes in the block and how to
interpret it after the transaction is canceled is entirely up to the
programmer. If the programmer chooses to preserve pointers that point
to memory allocated in the transaction and now rolled back, that's
his/her problem.

I am explicitly *not* try to allow for copying an entire, large
exception because I don't think it will ever makes sense, and therefore
there is no point in adding complexity to support it. I am open to
being convinced that there are important use cases in which we will need
to let large, complex exceptions escape from canceled transactions, but
until someone makes that case, I am fairly convinced we're risking
spending a lot of effort on features nobody will care about.

The poor man's version I'm suggesting at least gives programmers the
flexibility to retain a lot of information that may be helpful, but
let's remember that the transaction is being canceled, so the
information they need only has to inform a decision of what to do next
(retry, take remedial action before retrying, do something different,
report error, etc.). It does not need to contain precise information
for recovering from the situation from which the exception arose,
because this is being canceled anyway.

Finally, I agree with both of you that we don't want the compiler
quietly doing something dodgy, but I don't think that means we can't
have a poor man's solution along the lines I am suggesting. I think it
would be reasonable to consider it an error to throw something other
than an exception type that is known to be safe to throw. So
programmers would need to be sure to deal with large, complex exceptions
by (optionally) recording some useful information in the special area,
and then throwing a simple, safe exception that informs the code outside
the transaction that it has been canceled. That code can the look in
the special area to find whatever bits were stashed there.

I am under no illusions that what I am suggesting isn't quite ugly
compared to what you guys are suggesting. I just think it is much
simpler for specification and implementation, and again I think we are
talking about cases that will rarely make sense (wanting to preserve
large, complex exceptions out of canceled transactions), so I don't care
much about the additional burden on programmers in this case.

I should also say that I am suggesting this as what we include in our
*next* version of the spec, so that we could get some experience with
it, not that I necessarily think it's the right long-term solution for
eventual standardisation. *Maybe* it would result in people showing us
use cases that really want the full generality of copy constructors, in
which case it would then become clearer that the additional complexity
is worthwhile.

Alternatively, maybe we'd learn that nobody ever wants to do anything of
the sort, and it's always fine to throw a simple exception out of
canceled transactions. In that case, we could decide to impose a
stronger limitation and dodge this issue altogether.

Cheers

Mark

Torvald Riegel

unread,
Mar 11, 2013, 8:34:39 AM3/11/13
to t...@isocpp.org
On Mon, 2013-03-11 at 12:48 +1300, Mark Moir wrote:
> I am assuming something more primitive, which I think Torvald has
> clarified is what he refers to as "the memcpy approach". But I am *not*
> assuming that the entire exception needs to be able to escape, and I am
> suggesting that we don't need to give the programmer the convenience of
> general copy constructors for this purpose.
>
> I haven't thought through the details of questions Torvald raises much,
> but let's assume something like this for argument's sake:
>
> - I can call
>
> void* std::give_me_special_block()

This would at least need a size, I suppose. (Or another function to
query the size of this block.)

> to receive a pointer to my special, fixed-size block of memory that will
> not be rolled back upon cancellation.
>
> - when I call it again after the transaction is canceled, I get the same
> pointer again, so I know where to find the stuff I stashed before the
> transaction is canceled.

That sounds like a useful alternative interface to a memcpy. That's
somewhere between the very low-level memcpy and the allocation of
several escaping blocks of memory (like in the allocator-based approach
for the copy constructors). It's one special allocation, so not that
different in complexity from the latter :)

Furthermore, I think that how we can use an allocator-based approach to
enable copy constructors to run unmodified is not strictly tied to
whether we allow just one block or allocation of escaping memory. It's
just one of the things we can do with it.

> The original exception is destroyed as if it were caught and ignored in
> a catch block inside the canceled transaction.
>
> The choice of what the transaction writes in the block and how to
> interpret it after the transaction is canceled is entirely up to the
> programmer. If the programmer chooses to preserve pointers that point
> to memory allocated in the transaction and now rolled back, that's
> his/her problem.
>
> I am explicitly *not* try to allow for copying an entire, large
> exception because I don't think it will ever makes sense, and therefore
> there is no point in adding complexity to support it. I am open to
> being convinced that there are important use cases in which we will need
> to let large, complex exceptions escape from canceled transactions, but
> until someone makes that case, I am fairly convinced we're risking
> spending a lot of effort on features nobody will care about.

Just take an exception with an error message: Once we have variably
sized data that we need to copy, we start to need allocate the bytes
contained in the block. Thus, we'd just burden the programmer with
having to implement allocation.

> The poor man's version I'm suggesting at least gives programmers the
> flexibility to retain a lot of information that may be helpful, but
> let's remember that the transaction is being canceled, so the
> information they need only has to inform a decision of what to do next
> (retry,

That could indeed be a flag, or a pointer to a flag. That's one of the
simple cases.

> take remedial action before retrying,

So, do you think you can describe this easily with always the same
pieces of data? Especially in transactions in which we might do more
than one thing at once, we certainly need to distinguish those things,
and remedial actions associated with them. If we want this to be
composable, we need to be able to describe the remedial action in a
composable way. That's just burdensome if you just have a single
buffer; it's possible, but you then ask programmers to manage the buffer
explicitly. That's not a good trade-off, I think.

> do something different,
> report error,

Same as with the remedial actions: If you want to use transactions to
compose different things into one atomic piece, then I believe you want
to offer the same composability when describing errors. And that's
easier if you can use non-trivial data structures for it (e.g., even if
it just has several strings, or objects from a class hierarchy).

> etc.). It does not need to contain precise information
> for recovering from the situation from which the exception arose,
> because this is being canceled anyway.

I disagree. Once you cancel because you really failed, you need to
describe what went wrong. If you can't recover, you really need this
information. And even if you can recover and pick a different execution
path, you likely still want to know what went wrong just for the logging
aspect of this.

> Finally, I agree with both of you that we don't want the compiler
> quietly doing something dodgy, but I don't think that means we can't
> have a poor man's solution along the lines I am suggesting. I think it
> would be reasonable to consider it an error to throw something other
> than an exception type that is known to be safe to throw. So
> programmers would need to be sure to deal with large, complex exceptions
> by (optionally) recording some useful information in the special area,
> and then throwing a simple, safe exception that informs the code outside
> the transaction that it has been canceled. That code can the look in
> the special area to find whatever bits were stashed there.

I don't see a reason why we can't have a low-level mechanism such as the
special memcpy or explicit allocation of escaping memory *in addition*
to a more convenient approach. But just offering the low-level
mechanisms doesn't seem sufficient to me.

> I am under no illusions that what I am suggesting isn't quite ugly
> compared to what you guys are suggesting. I just think it is much
> simpler for specification and implementation

It's probably simpler for the specification, but not much I would say.
The core approach, designating a piece or several pieces of memory that
are not rolled back, is the same. Everything that the copy-constructor
approach adds could be specified on top of the low-level mechanisms, I
believe.

In terms of implementation, I don't think that there's much more effort
required to allocate several blocks instead of a single fixed block.
Unless you want to do something funny and use a special page and memory
access protection or such to treat the special block differently, I
don't see why both would be implemented wildly different. And even if
you use the former, you could still allocate all escaping blocks of
memory from that special page.

> , and again I think we are
> talking about cases that will rarely make sense (wanting to preserve
> large, complex exceptions out of canceled transactions), so I don't care
> much about the additional burden on programmers in this case.
>
> I should also say that I am suggesting this as what we include in our
> *next* version of the spec, so that we could get some experience with
> it, not that I necessarily think it's the right long-term solution for
> eventual standardisation. *Maybe* it would result in people showing us
> use cases that really want the full generality of copy constructors, in
> which case it would then become clearer that the additional complexity
> is worthwhile.

I think the data-escape feature would make for a good proposal for
discussion at Bristol. I could try to draft such a proposal this week.
What do people think? Any other volunteers (incl. volunteering
contributors) for this? What do you all think?


Torvald


Jens Maurer

unread,
Mar 11, 2013, 5:57:27 PM3/11/13
to t...@isocpp.org
On 03/11/2013 01:34 PM, Torvald Riegel wrote:
> I think the data-escape feature would make for a good proposal for
> discussion at Bristol. I could try to draft such a proposal this week.
> What do people think?

It seems to me that the data escape issue is one of the major
issues where fundamental semantics and expressive power are still
a bit unclear. (As opposed to some other features where we've
been discussing syntax and defaults and preferences up and down.)

Making progress for Bristol in that area would be cool.

Jens

Mark Moir

unread,
Mar 11, 2013, 6:33:46 PM3/11/13
to t...@isocpp.org

Hi Torvald

First, let me say that I am not strongly against a more elegant solution
along the lines you're proposing. After all, it's not me who has to
implement it, and also not me who has to specify it (that would be you
:-)). So I am just offering an observation that we should make sure we
consider whether these mechanisms will really matter *in practice* and
if we're not convinced that this is the case, consider whether a simpler
solution might suffice, at least for the short term.

I'd like to see you address the issue by convincing the group that these
cases are likely to be common enough to make even a little additional
complexity worthwhile.

There are a few responses below that maybe clarify my position and ask
you to clarify yours. But, as I said, I don't feel that strongly about
it and I'm not going to keep arguing about it. On this topic, I'm happy
to accept the judgment of you and Jens, even if it turns out to be wrong
:-).


On 3/12/13 1:34 AM, Torvald Riegel wrote:
> On Mon, 2013-03-11 at 12:48 +1300, Mark Moir wrote:
>> I am assuming something more primitive, which I think Torvald has
>> clarified is what he refers to as "the memcpy approach". But I am *not*
>> assuming that the entire exception needs to be able to escape, and I am
>> suggesting that we don't need to give the programmer the convenience of
>> general copy constructors for this purpose.
>>
>> I haven't thought through the details of questions Torvald raises much,
>> but let's assume something like this for argument's sake:
>>
>> - I can call
>>
>> void* std::give_me_special_block()
>
> This would at least need a size, I suppose. (Or another function to
> query the size of this block.)

I was assuming a fixed size, so that there would never be any need to
allocate any such memory within a transaction. One could imagine
specifying the size (e.g. via a library interface) before beginning
transaction execution and still getting this benefit though.

>
>> to receive a pointer to my special, fixed-size block of memory that will
>> not be rolled back upon cancellation.
>>
>> - when I call it again after the transaction is canceled, I get the same
>> pointer again, so I know where to find the stuff I stashed before the
>> transaction is canceled.
>
> That sounds like a useful alternative interface to a memcpy. That's
> somewhere between the very low-level memcpy and the allocation of
> several escaping blocks of memory (like in the allocator-based approach
> for the copy constructors). It's one special allocation, so not that
> different in complexity from the latter :)

As noted above, I intended it to be zero special allocations, at least
during transaction execution.

>
> Furthermore, I think that how we can use an allocator-based approach to
> enable copy constructors to run unmodified is not strictly tied to
> whether we allow just one block or allocation of escaping memory. It's
> just one of the things we can do with it.
>
>> The original exception is destroyed as if it were caught and ignored in
>> a catch block inside the canceled transaction.
>>
>> The choice of what the transaction writes in the block and how to
>> interpret it after the transaction is canceled is entirely up to the
>> programmer. If the programmer chooses to preserve pointers that point
>> to memory allocated in the transaction and now rolled back, that's
>> his/her problem.
>>
>> I am explicitly *not* try to allow for copying an entire, large
>> exception because I don't think it will ever makes sense, and therefore
>> there is no point in adding complexity to support it. I am open to
>> being convinced that there are important use cases in which we will need
>> to let large, complex exceptions escape from canceled transactions, but
>> until someone makes that case, I am fairly convinced we're risking
>> spending a lot of effort on features nobody will care about.
>
> Just take an exception with an error message: Once we have variably
> sized data that we need to copy, we start to need allocate the bytes
> contained in the block. Thus, we'd just burden the programmer with
> having to implement allocation.

You are assuming we need to preserve the entire message, no matter how
large it is. I am saying that I don't think so. The effects of the
transaction are going to be canceled, so we don't need precise
information to help us fix up what it did. If the error message is
human readable, and maybe is helpful for diagnosing the reason for the
failure so that the code can be changed to avoid it, I'd guess that if
the first 1K of the error message is insufficient, it's not a very good
error message :-).


>
>> The poor man's version I'm suggesting at least gives programmers the
>> flexibility to retain a lot of information that may be helpful, but
>> let's remember that the transaction is being canceled, so the
>> information they need only has to inform a decision of what to do next
>> (retry,
>
> That could indeed be a flag, or a pointer to a flag. That's one of the
> simple cases.

Can't follow this.


>
>> take remedial action before retrying,
>
> So, do you think you can describe this easily with always the same
> pieces of data?

Can't follow this either. What do you mean by "the same pieces of data"?

> Especially in transactions in which we might do more
> than one thing at once, we certainly need to distinguish those things,
> and remedial actions associated with them.

I'm not following this. Can you give an example?

> If we want this to be
> composable, we need to be able to describe the remedial action in a
> composable way. That's just burdensome if you just have a single
> buffer; it's possible, but you then ask programmers to manage the buffer
> explicitly. That's not a good trade-off, I think.

If the cases you're imagining are likely to be common, then I agree.
I'm just not convinced about that.

Cheers

Mark

Nevin Liber

unread,
Mar 11, 2013, 6:58:33 PM3/11/13
to t...@isocpp.org
On 11 March 2013 17:33, Mark Moir <mark...@oracle.com> wrote:
I was assuming a fixed size, so that there would never be any need to allocate any such memory within a transaction.

So how does this work when there are multiple exceptions in play, either on the same thread or in separate threads?  For instance, the following is currently legal:

try { Foo(1); }
catch (exception& e1) {
    try { Foo(2); }
    catch (exception& e2) { /* handle e2 */ }
    throw;  // rethrow e1
}

I don't see how we can expect the user to manage the memory.

(Going back under my rock,)
 Nevin :-)
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Mark Moir

unread,
Mar 11, 2013, 7:14:02 PM3/11/13
to t...@isocpp.org

Again I am not arguing strongly for this clearly-ugly simplification for
what I think will be uncommon use cases. But still I will answer the
question...

On 3/12/13 11:58 AM, Nevin Liber wrote:
> On 11 March 2013 17:33, Mark Moir <mark...@oracle.com
> <mailto:mark...@oracle.com>> wrote:
>
> I was assuming a fixed size, so that there would never be any need
> to allocate any such memory within a transaction.
>
>
> So how does this work when there are multiple exceptions in play, either
> on the same thread or in separate threads? For instance, the following
> is currently legal:
>
> try { Foo(1); }
> catch (exception& e1) {
> try { Foo(2); }
> catch (exception& e2) { /* handle e2 */ }
> throw; // rethrow e1
> }
>
> I don't see how we can expect the user to manage the memory.

Nothing changes until the point at which an exception is going to escape
from a to-be-canceled exception. At the point of the throw (rethrow of
e1), if e1 is not of a type that is safe to throw unmodified, then at
that point, the programmer would write whatever information s/he deems
necessary into the single, fixed-sized, special area, and then throw an
exception that is safe to throw (perhaps that indicates that there is
information in the special area and/or indicating where that special
area is, depending on other details).

The only "memory management" challenge for the programmer is how to
distill the contents of a large and complex exception down into a single
fixed-sized buffer. Note that this is done within the transaction, with
full visibility into the entire exception. So, if it's possible to
decide what to do next based on the contents of the exception, and that
decision can be encoded in the fixed-size buffer, then there is no
problem. The problem is if there really is a need to get the entire
exception out of the canceled transaction. I am open to the possibility
that this will often make sense, but again I would like to see some
examples to convince me.

Cheers

Mark




>
> (Going back under my rock,)
> Nevin :-)
> --
> Nevin ":-)" Liber <mailto:ne...@eviloverlord.com
> <mailto:ne...@eviloverlord.com>> (847) 691-1404

Nevin Liber

unread,
Mar 11, 2013, 7:40:25 PM3/11/13
to t...@isocpp.org
On 11 March 2013 18:14, Mark Moir <mark...@oracle.com> wrote:

try { Foo(1); }
catch (exception& e1) {
     try { Foo(2); }
     catch (exception& e2) { /* handle e2 */ }
     throw;  // rethrow e1
}

I don't see how we can expect the user to manage the memory.

Nothing changes until the point at which an exception is going to escape from a to-be-canceled exception.  At the point of the throw (rethrow of e1), if e1 is not of a type that is safe to throw unmodified, then at that point, the programmer would write whatever information s/he deems necessary into the single, fixed-sized, special area, and then throw an exception that is safe to throw (perhaps that indicates that there is information in the special area and/or indicating where that special area is, depending on other details).

That memory is a shared global resource.

What happens if the constructor of "exception" when called inside Foo() does the writing to this shared global resource?  Both calls to Foo, if they fail, will try and write to that same memory.  Do I need more shared global state to manage that?  How do I make sure that state isn't rolled back until the exception is processed?  How is that handled across different exception types, which may not know about each other?

Plus, for multiple threads, is there separate memory for each thread, or is the user expected to have a mutex protecting the memory?  And isn't the point of TM so that we don't have to litter our code with mutexes to synchronize access to shared resources?
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Mark Moir

unread,
Mar 11, 2013, 7:49:55 PM3/11/13
to t...@isocpp.org


On 3/12/13 12:40 PM, Nevin Liber wrote:
> On 11 March 2013 18:14, Mark Moir <mark...@oracle.com
> <mailto:mark...@oracle.com>> wrote:
>
>
> try { Foo(1); }
> catch (exception& e1) {
> try { Foo(2); }
> catch (exception& e2) { /* handle e2 */ }
> throw; // rethrow e1
> }
>
> I don't see how we can expect the user to manage the memory.
>
>
> Nothing changes until the point at which an exception is going to
> escape from a to-be-canceled exception. At the point of the throw
> (rethrow of e1), if e1 is not of a type that is safe to throw
> unmodified, then at that point, the programmer would write whatever
> information s/he deems necessary into the single, fixed-sized,
> special area, and then throw an exception that is safe to throw
> (perhaps that indicates that there is information in the special
> area and/or indicating where that special area is, depending on
> other details).
>
>
> That memory is a shared global resource.

Who says? This is *my* half-baked ugly proposal :-). Seriously, I was
suggesting that the memory would be allocated (if necessary) before
beginning execution of a transaction. The memory would be private to
that thread and transaction.


>
> What happens if the constructor of "exception" when called inside Foo()
> does the writing to this shared global resource?

I am suggesting that this memory will be written only explicitly by the
programmer who is distilling/summarising the exception that's about to
be lost because it's not safe to escape from a canceled transaction.

I don't think there is much point in discussing this in too much more
depth because the tide is against my suggestion here, and I don't care
too much if this suggestion is not accepted.

I just wanted to provoke some discussion to convince us that we are
addressing reasonable use cases here. If the result is that we don't
think so, we could consider something even simpler (e.g., just don't
allow any exception to escape other than those that are safe to escape;
terminate if anyone tries to throw anything else). But I guess people
seem to think this case matters, so who am I to stand in their way? :-)

Cheers

Mark


> Both calls to Foo, if
> they fail, will try and write to that same memory. Do I need more
> shared global state to manage that? How do I make sure that state isn't
> rolled back until the exception is processed? How is that handled
> across different exception types, which may not know about each other?
>
> Plus, for multiple threads, is there separate memory for each thread, or
> is the user expected to have a mutex protecting the memory? And isn't
> the point of TM so that we don't have to litter our code with mutexes to
> synchronize access to shared resources?
> --
> Nevin ":-)" Liber <mailto:ne...@eviloverlord.com

Torvald Riegel

unread,
Mar 12, 2013, 7:20:11 AM3/12/13
to t...@isocpp.org
That would be fine too. But if you allow the size to be specified, this
would be another reason for having to allocate the buffer at runtime
(see below for why that matters).

> >
> >> to receive a pointer to my special, fixed-size block of memory that will
> >> not be rolled back upon cancellation.
> >>
> >> - when I call it again after the transaction is canceled, I get the same
> >> pointer again, so I know where to find the stuff I stashed before the
> >> transaction is canceled.
> >
> > That sounds like a useful alternative interface to a memcpy. That's
> > somewhere between the very low-level memcpy and the allocation of
> > several escaping blocks of memory (like in the allocator-based approach
> > for the copy constructors). It's one special allocation, so not that
> > different in complexity from the latter :)
>
> As noted above, I intended it to be zero special allocations, at least
> during transaction execution.

I agree regarding that you don't need the library to allocate during the
transaction (but the transaction itself will have to manage the memory
in the buffer, that is, allocating space from the buffer).
However, from the implementation POV, even this buffer is like
dynamically allocated memory because it likely cannot reside at a
position fixed before runtime: we have one buffer per thread, the
buffer's size might be different, etc. So if that's the case, there
can't be fixed checks against the buffer's memory addresses in the TM
implementation, but they would have to be dynamic. So, not much
difference to having dynamically allocated buffers during runtime.

We could have a fixed global buffer from which all threads allocate, and
the TM could just not rollback anything in this global buffer (so
threads should better not write to other thread's regions in this
buffer). But even with such a scheme, dynamic allocation during
transactions could allocate space in this fixed buffer too.

Thus, I don't see any substantial differences in what implementations
would need to do. Do you see any?

> >
> > Furthermore, I think that how we can use an allocator-based approach to
> > enable copy constructors to run unmodified is not strictly tied to
> > whether we allow just one block or allocation of escaping memory. It's
> > just one of the things we can do with it.
> >
> >> The original exception is destroyed as if it were caught and ignored in
> >> a catch block inside the canceled transaction.
> >>
> >> The choice of what the transaction writes in the block and how to
> >> interpret it after the transaction is canceled is entirely up to the
> >> programmer. If the programmer chooses to preserve pointers that point
> >> to memory allocated in the transaction and now rolled back, that's
> >> his/her problem.
> >>
> >> I am explicitly *not* try to allow for copying an entire, large
> >> exception because I don't think it will ever makes sense, and therefore
> >> there is no point in adding complexity to support it. I am open to
> >> being convinced that there are important use cases in which we will need
> >> to let large, complex exceptions escape from canceled transactions, but
> >> until someone makes that case, I am fairly convinced we're risking
> >> spending a lot of effort on features nobody will care about.
> >
> > Just take an exception with an error message: Once we have variably
> > sized data that we need to copy, we start to need allocate the bytes
> > contained in the block. Thus, we'd just burden the programmer with
> > having to implement allocation.
>
> You are assuming we need to preserve the entire message, no matter how
> large it is.

This is sort of like saying that file names never need to be longer than
8+3 characters. And of course memory is limited, but the size of the
buffer is not really the point; rather, it's about how to manage the
space the buffer provides.

> I am saying that I don't think so. The effects of the
> transaction are going to be canceled, so we don't need precise
> information to help us fix up what it did.

I strongly disagree. It's true that we do not need to know about the
error to be able to roll back, but we do need to know about it to ensure
forward progress. If we need to avoid the fault, we need to know about
the fault. If we need to report the error, then the more information we
can provide the better.

> If the error message is
> human readable, and maybe is helpful for diagnosing the reason for the
> failure so that the code can be changed to avoid it, I'd guess that if
> the first 1K of the error message is insufficient, it's not a very good
> error message :-).

The question of the size of the buffer is IMO not the primary issue
here, but I also disagree with it:
* If we want to understand an error, we don't just need to know the
error code (eg, the errno value) but also how we got there. For example,
an "access denied" error isn't helpful at all until you know which file
or whatever the program tried to access. Or which URL. Or both. And
so on. So, overall, I think it's pretty simple to get a lot of data.

Even if there's no I/O involved the error description could be quite
long. In the account money transfer example, we at least have to
identify both accounts. So this is stuff like IBAN+BIC, perhaps account
holder, date+time, etc. Perhaps the program uses XML to do that, so
that it can be parsed easily. And again we're gathering quite a bit of
data.

> >
> >> The poor man's version I'm suggesting at least gives programmers the
> >> flexibility to retain a lot of information that may be helpful, but
> >> let's remember that the transaction is being canceled, so the
> >> information they need only has to inform a decision of what to do next
> >> (retry,
> >
> > That could indeed be a flag, or a pointer to a flag. That's one of the
> > simple cases.
>
> Can't follow this.

Whether we retry or not could be represented as a boolean value (ie, a
flag).

>
>
> >
> >> take remedial action before retrying,
> >
> > So, do you think you can describe this easily with always the same
> > pieces of data?
>
> Can't follow this either. What do you mean by "the same pieces of data"?

The remedial actions that are required from the POV of the transaction
need to either be referenced or described. If you have a single buffer,
and need to describe them in a nontrivial way, the programmer is
required to manage the space provided by the buffer.

If we do two actions in a transaction, and either of both might fail and
need a different remedial action, then we might have to describe or
reference two different things. We might be lucky to be able to
describe them using the same data, but perhaps we need different pieces
of data to describe them. If the latter, the programmer has to
serialize the description of the selected remedial action in the
transaction, and parse it again after the transaction (the buffer could
describe two different remedial actions...)

>
> > Especially in transactions in which we might do more
> > than one thing at once, we certainly need to distinguish those things,
> > and remedial actions associated with them.
>
> I'm not following this. Can you give an example?

Is the above example clear enough?


Torvald

Torvald Riegel

unread,
Mar 12, 2013, 8:01:58 AM3/12/13
to t...@isocpp.org
On Tue, 2013-03-12 at 12:14 +1300, Mark Moir wrote:
And that's more difficult than I believe that you think, and can quickly
get error-prone.

The simple case is when you have a single variable (e.g., one of the
trivial types) to copy, or an exception that can be copied with just a
memcpy call.

But as soon as the exception is not represented by a continuous chunk of
memory, this starts to become nontrivial. Then, you have to either
transform it into such a chunk, or you have to allocate space from the
buffer. The latter burdens you (ie, the programmer) with implementing
an allocator.

If you additionally have variably-sized objects (e.g., strings), you
must manage allocation. And when doing that, you have to also pay
attention to things like alignment requirements. Also, if we have
strings, we prevent programmers from using std::string and instead
require them to handle strings explicitly with calls to str*(), like in
plain C.

Just try to write down example escape code for the following exceptions
using the single special buffer (both sides: setting the buffer's
contents and parsing it):
1) 1 int value, 1 string
2) 1 int value, 1 float, 1 string
3) 2 strings

I'd argue none of the 3 kinds of exception is an absurdly complex
exception. For example, 3) would be needed to describe the account
holders in the money transfer example.

> Note that this is done within the transaction, with
> full visibility into the entire exception. So, if it's possible to
> decide what to do next based on the contents of the exception, and that
> decision can be encoded in the fixed-size buffer, then there is no
> problem.

Even if programmers can use this to get it done, we still have a problem
if it's just too hard to use in a non-error-prone way.

> The problem is if there really is a need to get the entire
> exception out of the canceled transaction. I am open to the possibility
> that this will often make sense, but again I would like to see some
> examples to convince me.

I hope the example in my other email suffice.

Torvald

Mark Moir

unread,
Mar 12, 2013, 5:22:51 PM3/12/13
to t...@isocpp.org

Hi Torvald

I will answer your emails, as it's interesting to discuss use cases to
see different peoples' ideas about how TM will be used. But I don't
plan to answer them this week, as I don't think this discussion is
productive for the deadlines this week: I've already said I am happy to
accept your judgment that my suggestion is no good, even short-term.

I'd much prefer to see you present a clean description of what you
propose (note, it does not have to include implementation detail, as
long as you and others are confident it's reasonable) so that we can
include it in the summary document.

If we are not going to able to agree on something in the next day or
two, then we'll need to leave this issue as "to be resolved" in the
summary, even though we can mention activity and progress towards
resolving it. I think this is a great time to try to make some forward
progress on this issue. It's been hanging around for a while, and I
don't think there's anything new here except perhaps for wider agreement
that we need to address the issue.

If you can provide even a paragraph or two describing the approach, I
can incorporate it into the summary document, and perhaps Victor can
incorporate into the draft next spec document (but that would require
more precise detail from you if he is actually going to reflect
language-level details).

It needs to be pretty soon, at least if you want my feedback on it, as I
will be traveling starting Thursday evening NZ time and will then be
pretty much off the radar until after the mailing deadline.

Cheers

Mark

Torvald Riegel

unread,
Mar 13, 2013, 5:26:22 PM3/13/13
to t...@isocpp.org
On Wed, 2013-03-13 at 10:22 +1300, Mark Moir wrote:
> If we are not going to able to agree on something in the next day or
> two, then we'll need to leave this issue as "to be resolved" in the
> summary, even though we can mention activity and progress towards
> resolving it. I think this is a great time to try to make some forward
> progress on this issue. It's been hanging around for a while, and I
> don't think there's anything new here except perhaps for wider agreement
> that we need to address the issue.

I thought about putting the escape feature into a separate proposal, but
it has somewhat of a dependence on the cancellation feature in that it
needs to specify to which nesting level (or conceptually similar) the
data should escape. OTOH, we could just support escape to nontxnal for
now and add anything required for closed-nesting escape later on, I
believe (w/o any fundamental changes to how this works).

> If you can provide even a paragraph or two describing the approach, I
> can incorporate it into the summary document, and perhaps Victor can
> incorporate into the draft next spec document (but that would require
> more precise detail from you if he is actually going to reflect
> language-level details).

I had assumed that this is still under too much discussion to put it in
the next spec doc, and that it thus should go into a separate proposal
instead. But maybe not -- I have no strong opinion on this...

> It needs to be pretty soon, at least if you want my feedback on it, as I
> will be traveling starting Thursday evening NZ time and will then be
> pretty much off the radar until after the mailing deadline.

I'll try; but surprise work turned up. So I'll have to see how it goes.
I'll try to get the escape feature description done first.

Torvald

Mark Moir

unread,
Mar 13, 2013, 6:08:51 PM3/13/13
to t...@isocpp.org


On 3/14/13 10:26 AM, Torvald Riegel wrote:
> On Wed, 2013-03-13 at 10:22 +1300, Mark Moir wrote:
>> If we are not going to able to agree on something in the next day or
>> two, then we'll need to leave this issue as "to be resolved" in the
>> summary, even though we can mention activity and progress towards
>> resolving it. I think this is a great time to try to make some forward
>> progress on this issue. It's been hanging around for a while, and I
>> don't think there's anything new here except perhaps for wider agreement
>> that we need to address the issue.
>
> I thought about putting the escape feature into a separate proposal, but
> it has somewhat of a dependence on the cancellation feature in that it
> needs to specify to which nesting level (or conceptually similar) the
> data should escape. OTOH, we could just support escape to nontxnal for
> now and add anything required for closed-nesting escape later on, I
> believe (w/o any fundamental changes to how this works).

I don't understand the issue here. I think we are just talking about
the issue of what happens when a "nontrivial" exception escapes a
canceled transaction. Whatever information is made available to the
surrounding context, I don't see any dependence on whether that context
is within another transaction or not. (I can see that there may be
issues that implementations need to take into account, but I can't see
any reason it affects the specification. If you do, can you elaborate?)

FWIW, I am assuming that this information (whatever escapes the
transaction when a nontrivial exception would escape) is thread-private.
So, for example, we would not need to worry about the case in which
another thread modifies the information while it's being read by the
thread that executed the canceled transaction, which might raise the
question of whether this means that if the canceled transaction was
nested within another transaction, the latter transaction conflicts with
such updates. I think that clearly makes sense, as no other thread
should have seen the effects of the canceled transaction (including
whatever information escapes from it) if it is still nested within
another transaction.

>
>> If you can provide even a paragraph or two describing the approach, I
>> can incorporate it into the summary document, and perhaps Victor can
>> incorporate into the draft next spec document (but that would require
>> more precise detail from you if he is actually going to reflect
>> language-level details).
>
> I had assumed that this is still under too much discussion to put it in
> the next spec doc, and that it thus should go into a separate proposal
> instead. But maybe not -- I have no strong opinion on this...

It's true that we have not agreed on anything. But there is progress in
the discussion and I think it would be wasteful not to capture that into
the documents we submit for Bristol, if we have something coherent and
complete enough to include.

Presumably decisions reflected in these documents are not set in stone,
though obviously we don't want to keep opening old issues. My opinion
is that, if you have time to get something written up, we should include
it. If anyone is uncomfortable that it's being "rushed through", we can
always reflect that this is the current proposal under discussion, but
it may change even before the next version of the spec. At least this
way there is a chance for a concrete discussion of the issue and to get
feedback on your proposed direction, as well as just showing people that
we are discussing reasonable alternatives here, and it's not just a
problem we have no idea how to deal with it.

Looks like I'll be leaving this decision to you guys to make and
implement, so the above is my 2c.

Cheers

Mark

Boehm, Hans

unread,
Mar 13, 2013, 6:31:04 PM3/13/13
to t...@isocpp.org
Building on Torvald's and Mark's discussion, it seems to me that a really minimal proposal would be:

1) If a transaction is cancelled as a result of an exception, the cancelled transaction throws a fixed, zero information, transaction_cancelled exception. (Alternatively, and perhaps less minimally, this happens for "non-trivial" exceptions, and "trivial" exceptions just pass through.)

2) The standard defines a

thread_local vector<char, impl_defined_allocator> cancel_immune_state;

which survives (is not rolled back during) explicit cancellation.

3) (Optionally, less minimally:) Perhaps we provide some library support for serializing standard exception types into a vector<char>, and the inverse.

This would allow someone who needs more information to catch the exception before leaving the transaction, serialize its content into cancel_immune_state, catch transaction_cancelled outside the transaction, and deserialize cancel_immune_state into a real exception. All of this seems slightly painful, but I think the pain could be factored out into a library routine that takes a callable, which is the transaction body. And the semantics seem fairly clean and understandable. And it seems error prone only to the extent the explicit serialization and deserialization is, which I think is less of an issue than alternatives that subtly break reference counting or the like.

In a Haswell-like context, writing to cancel_immune_state could presumably abort and switch to STM mode. With the intended usage model, I think that has reasonable performance characteristics, and allows an ambitious programmer to make use of Haswell (or similar) explicit aborts without always switching to STM mode.

It seems to me that even if we just included (1) and (2) for now, this would give us some reading as to whether we should provide more. If nobody complains that this is really clumsy, and the library to factor out the boilerplate doesn't materialize, we're done.

Opinions?

Hans


Torvald Riegel

unread,
Mar 14, 2013, 7:32:05 AM3/14/13
to t...@isocpp.org
On Thu, 2013-03-14 at 11:08 +1300, Mark Moir wrote:
>
> On 3/14/13 10:26 AM, Torvald Riegel wrote:
> > On Wed, 2013-03-13 at 10:22 +1300, Mark Moir wrote:
> >> If we are not going to able to agree on something in the next day or
> >> two, then we'll need to leave this issue as "to be resolved" in the
> >> summary, even though we can mention activity and progress towards
> >> resolving it. I think this is a great time to try to make some forward
> >> progress on this issue. It's been hanging around for a while, and I
> >> don't think there's anything new here except perhaps for wider agreement
> >> that we need to address the issue.
> >
> > I thought about putting the escape feature into a separate proposal, but
> > it has somewhat of a dependence on the cancellation feature in that it
> > needs to specify to which nesting level (or conceptually similar) the
> > data should escape. OTOH, we could just support escape to nontxnal for
> > now and add anything required for closed-nesting escape later on, I
> > believe (w/o any fundamental changes to how this works).
>
> I don't understand the issue here. I think we are just talking about
> the issue of what happens when a "nontrivial" exception escapes a
> canceled transaction. Whatever information is made available to the
> surrounding context, I don't see any dependence on whether that context
> is within another transaction or not. (I can see that there may be
> issues that implementations need to take into account, but I can't see
> any reason it affects the specification. If you do, can you elaborate?)

If I want to have a generic escape feature that is not just used in a
controlled way by the cancellation feature, then in an example like
transcation {
transaction {
escape_data(...)
}}
I need to know whether the data should escape to nontxnal or the
outermost transaction. If the escape feature is just used by
cancellation, then it's clear where it should escape to (and the
implementation is aware of this too).

> FWIW, I am assuming that this information (whatever escapes the
> transaction when a nontrivial exception would escape) is thread-private.

Agreed.


Torvald Riegel

unread,
Mar 14, 2013, 7:38:14 AM3/14/13
to t...@isocpp.org
On Wed, 2013-03-13 at 22:31 +0000, Boehm, Hans wrote:
> In a Haswell-like context, writing to cancel_immune_state could presumably abort and switch to STM mode. With the intended usage model, I think that has reasonable performance characteristics, and allows an ambitious programmer to make use of Haswell (or similar) explicit aborts without always switching to STM mode.

How do you detect when a HW transaction tries to write to
cancel_immune_state? With Haswell and similar, we don't want to run
instrumented code, so the only option that I see is using non-writable
pages for cancel_immune_state. But then we need to have a different
cancel_immune_state in STM mode too.

Torvald Riegel

unread,
Mar 14, 2013, 8:01:38 AM3/14/13
to t...@isocpp.org
On Wed, 2013-03-13 at 22:26 +0100, Torvald Riegel wrote:
> I'll try to get the escape feature description done first.

Haven't finished this, but I think I have a clean cancel interface now.
I'll just show some code pieces here; the full description will come
later. Perhaps you have some feedback already.

Compared to the previous WIP proposal, failure handler lambdas are
almost gone but the progress handler lambda stays, and I'll now call it
cancellation handler (ch_lambda):

__transaction_atomic (ch_lambda) { ... }

ch_lambda is optional; if not present, this txn can't be canceled. If
you still cancel, std::terminate. You can either supply a real lambda
or a pointer to one. In case of latter, the pointer also identifies the
txn, so you can use the pointer to cancel exactly this txn. So, we can
do flat nesting unless there is a ch_lambda specified, and we can decide
this at compile time.

Iff the txn is canceled, ch_lambda is executed after the txn has rolled
back. Informally, it's thus executed instead of the txn. The labmda
takes no args and has void as return type (see below for with-arg
options).

There are basically 3 forms of cancellation():

1) cancel()
Just cancels the nearest enclosing txn (in terms of nesting levels) that
has a ch_lambda, or the outermost.

2) cancel(ch_lambda_type* txn)
Cancels the given txn. There are different possibilities here, but we
could for example say that this selects the nearest enclosing txn with a
ch_handler addr equal to txn.

3) cancel_wrap(ch_lambda_type* txn, ch_lambda_wrapper_type ch_wrapper)
Informally, this lets a lambda escape from the txn; the lambda can then
call the canceled txn's ch_handler, which is supplied to it using an
argument. This will create an *escaping* copy of the ch_wrapper lambda.
This copy is then executed after the txn has been rolled back.
There are other options for how to express this, but I like this one so
far. The benefit compared to 1) is that this doesn't need external
storage for escaping data or keeping a pointer to escaping data (this is
all in the lambda), and the txn can influence the code executed after
cancellation. So you can do all sorts of funny things with it,
including throwing exceptions out of canceled txns with just a suitable
lambda for it:
cancel_wrap(my_txn, [] (ch_lambda_type* txn) { throw foobar; } );
This also allows for ch_lambdas that take arguments; type safety can be
ensured by checking the type of my_txn against the ch_wrapper type's
argument.

Hope this is clear in even the short form (yes, there are details that
need to be specified). Cancel-on-escape could then be done like this:
__transaction_atomic (std::cancel_on_escape()) {
try {
...; // txn body
} catch(...) {
cancel_wrap(std::cancel_on_escape(), [] (ch_lambda_type* ignore) {
throw canceled_exception;
});
}
}
In that case, we wouldn't even have to copy+escape any lambda, because
this doesn't capture any data.

Thoughts?

Torvald

Niall Douglas

unread,
Mar 15, 2013, 10:44:26 AM3/15/13
to t...@isocpp.org
On Wednesday, March 13, 2013 6:31:04 PM UTC-4, Hans Boehm wrote:

In a Haswell-like context, writing to cancel_immune_state could presumably abort and switch to STM mode.  With the intended usage model, I think that has reasonable performance characteristics, and allows an ambitious programmer to make use of Haswell (or similar) explicit aborts without always switching to STM mode.

It seems to me that even if we just included (1) and (2) for now, this would give us some reading as to whether we should provide more.  If nobody complains that this is really clumsy, and the library to factor out the boilerplate doesn't materialize, we're done.

Opinions?

I think this is a very useful post, not least because it is framed around forthcoming real commodity hardware. And as I've mentioned here before, I only have interest in what the hardware can accelerate.

That said, I don't like (a) magic exception types or (b) magic vector types. So what would I propose instead?

I have a very useful class which I use a *lot* in my own coding:

template<typename callable> class UndoerImpl
{
 callable undoer
;
 
bool _dismissed;


#if !defined(_MSC_VER) || _MSC_VER>1700
 
UndoerImpl() = delete;
 
UndoerImpl(const UndoerImpl &) = delete;
 
UndoerImpl &operator=(const UndoerImpl &) = delete;
#else
 
UndoerImpl();
 
UndoerImpl(const UndoerImpl &);
 
UndoerImpl &operator=(const UndoerImpl &);
#endif
 
explicit UndoerImpl(callable &&c) : undoer(std::move(c)), _dismissed(false) { }
 
void int_trigger() { if(!_dismissed && !is_nullptr(undoer)) { undoer(); _dismissed=true; } }
public:
 
UndoerImpl(UndoerImpl &&o) : undoer(std::move(o.undoer)), _dismissed(o._dismissed) { o._dismissed=true; }
 
UndoerImpl &operator=(UndoerImpl &&o) { int_trigger(); undoer=std::move(o.undoer); _dismissed=o._dismissed; o._dismissed=true; return *this; }
 
template<typename _callable> friend UndoerImpl<_callable> Undoer(_callable c);
 
~UndoerImpl() { int_trigger(); }
 
//! Returns if the Undoer is dismissed
 
bool dismissed() const { return _dismissed; }
 
//! Dismisses the Undoer
 
void dismiss(bool d=true) { _dismissed=d; }
 
//! Undismisses the Undoer
 
void undismiss(bool d=true) { _dismissed=!d; }
};

/*! \brief Alexandrescu style rollbacks, a la C++ 11.

Example of usage:
\code
auto resetpos=Undoer([&s]() { s.seekg(0, std::ios::beg); });
...
resetpos.dismiss();
\endcode
*/

template<typename callable> inline UndoerImpl<callable> Undoer(callable c)
{
 
//static_assert(!std::is_function<callable>::value && !std::is_member_function_pointer<callable>::value && !std::is_member_object_pointer<callable>::value && !has_call_operator<callable>::value, "Undoer applied to a type not providing a call operator");
 
auto foo=UndoerImpl<callable>(std::move(c));
 
return foo;
}


This lets you write undo lambdas as you write code, thereby when an exception throws it all gets unwound nicely. Effectively speaking *all* your C++ becomes is a set of soft transactions on state. And the above is *extremely* natural to program in, and it easily becomes second nature to write. I don't get that latter feeling about any of the C++ STM I've seen proposed here so far, and for me at least, I think that is a big impediment.

Now what I'd really like to see in a decent hardware accelerated C++ STM implementation is a "speculative transaction" (the above are unwinds *after* the effect, a speculative transaction would be *before* the effect). I even think an "exception like" system which is completely held separate at the syntax level from C++ exceptions might be an idea. And I don't think magic containers are necessary when Haswell's STM at least is basically up to seven nested try...failed handlers.

But as I said earlier on this group, I'm still too ignorant of the technology to say much more than gut feelings: obviously malloc inside a STM needs to be supported, but it's a real pain to see how because you'll run out of nesting levels very quickly if you permit it (I certainly can see three or four STMs being used inside malloc's implementation alone). It's a bit like your reaction Hans to my early proposals of C11 standardised wait objects some years ago: one can be sure of what one doesn't like easier than what one does like.

Niall

Victor Luchangco

unread,
Mar 29, 2013, 10:55:54 AM3/29/13
to t...@isocpp.org
Hi folks,

Things have been relatively quiet since our big push a couple of weeks ago, but we really should make progress on at least one issue before Bristol: how to handle exceptions escaping from a cancel-on-escape atomic transaction. As Niall, Jens and others have pointed out, it would be better to have some simple mechanism that we can experiment with than nothing at all. In the spirit of the "minimalist proposal", I propose that we adopt the following restrictive model for now:

We specify a set of exception types that are allowed to escape from a cancel-on-escape transaction (cancelling the transaction as it escapes). These exceptions are propagated unchanged beyond the transaction. Other exceptions that attempt to escape a cancel-on-escape transaction result in termination. I propose the following "cancel-friendly" exception types:

exceptions "of integral or enumerated type"
bad_alloc

The former were allowed to be thrown by the cancel-and-throw statement in the draft specification. The latter is a case we think may be common and important, and I don't think it causes any problems (but please point out any problems I may have overlooked). Are there any others we should add to this list?

This approach avoids the need to specify any mechanism to handle exceptions that need special treatment, leaving open the possibility of adding such mechanisms in the future. Like many others, I think we may ultimately want to have such mechanisms, but this simple proposal handles many of the cases that are most likely to arise and that can be handled straightforwardly and with little to no conceptual overhead for the programmer. I hope that we can quickly agree to this proposal (or something like it), so we can present it as Bristol and use it as a basis for future discussions.

- Victor

PS. I've deliberately kept this short and relatively self-contained so that people can weigh in without reading a long thread. For reference, the thread titled "Exporting information from a cancelled transaction" discusses many relevant issues, most of which are about mechanisms for handling a richer set of exception types, which I want to avoid with this proposal. And of course, this proposal builds on my "minimalist proposal for atomic transactions" proposal from Feb 14.

Michael L. Scott

unread,
Mar 29, 2013, 8:25:01 PM3/29/13
to t...@isocpp.org
This sounds reasonable to me.

Niall Douglas

unread,
Apr 1, 2013, 11:32:42 AM4/1/13
to t...@isocpp.org
On Friday, March 29, 2013 10:55:54 AM UTC-4, Victor Luchangco @ Oracle wrote:
We specify a set of exception types that are allowed to escape from a cancel-on-escape transaction (cancelling the transaction as it escapes).  These exceptions are propagated unchanged beyond the transaction.  Other exceptions that attempt to escape a cancel-on-escape transaction result in termination.  I propose the following "cancel-friendly" exception types:

   exceptions "of integral or enumerated type"
   bad_alloc

The former were allowed to be thrown by the cancel-and-throw statement in the draft specification.  The latter is a case we think may be common and important, and I don't think it causes any problems (but please point out any problems I may have overlooked).  Are there any others we should add to this list?

I think, from a compiler writer's viewpoint, a "minimalist proposal" probably equals transactions always being cancelled/process terminated as soon as the try or throw keywords are executed. In fact, perhaps the best minimalist proposal, for now at least, is one which basically allows the use of C and little of C++ e.g. anything with unbounded execution times like dynamic_cast<> is verboten, at least at the beginning.

I read recently that Haswell's STM support is going to be used as a product differentiator, so it won't be enabled in lower end chips. That means in practice that hardware assisted STM won't really become available till after Haswell, and by which stage I'd assume ARM will have added their assist which could be quite different.

I am therefore even more cautious and conservative than I was before because my ignorance level has just jumped by an order or two. I feel extremely uncertain that now is the time for STM in C++. Let me put this another way: I think C++ STM should take the highly successful OpenMP approach: implement a just sufficient #pragma based subset for C first, and we'll go from there (see http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3530.pdf for the proposed integration of OpenMP syntax into C++).

That said I'll be at C++ Now 2013, and will attend the talk on C++ STM there. I can be convinced I'm being too paranoid.

Niall

Mark Moir

unread,
Apr 1, 2013, 8:19:58 PM4/1/13
to t...@isocpp.org


On 4/2/13 4:32 AM, Niall Douglas wrote:
> On Friday, March 29, 2013 10:55:54 AM UTC-4, Victor Luchangco @ Oracle
> wrote:
>
> We specify a set of exception types that are allowed to escape from
> a cancel-on-escape transaction (cancelling the transaction as it
> escapes). These exceptions are propagated unchanged beyond the
> transaction. Other exceptions that attempt to escape a
> cancel-on-escape transaction result in termination. I propose the
> following "cancel-friendly" exception types:
>
> exceptions "of integral or enumerated type"
> bad_alloc
>
> The former were allowed to be thrown by the cancel-and-throw
> statement in the draft specification. The latter is a case we think
> may be common and important, and I don't think it causes any
> problems (but please point out any problems I may have overlooked).
> Are there any others we should add to this list?
>
>
> I think, from a compiler writer's viewpoint, a "minimalist proposal"
> probably equals transactions always being cancelled/process terminated
> as soon as the try or throw keywords are executed. In fact, perhaps the
> best minimalist proposal, for now at least, is one which basically
> allows the use of C and little of C++ e.g. anything with unbounded
> execution times like dynamic_cast<> is verboten, at least at the beginning.

We've probably used the word "minimal" somewhat inadvisedly here. You
are right that the "small and simple" proposal Victor has made is not
technical "minimal". However, if we say it's a minimal relaxation of
the restrictions imposed by the previous version of the spec, that might
be closer to the truth (it's essentially just adding std::bad_alloc to
the list of exceptions that can escape canceled transactions).

I think the distinction is important, because what we're really trying
to do is to enable more learning about the kinds of things people want
to do. We already know from experience that if allocation within
transactions is forbidden, that is enough of a disadvantage that people
will say "thanks, let me know when you've addressed that and then I'll
try it out".

The problem is not really to do with try, throw, or catch *within* the
transaction. It's all about what happens when an exception *escapes*
from a transaction. So with Victor's proposal, people could do
something like this:



while (1) {
try {
__transaction_atomic [[cancel_on_escape]] {
try {
<whatever>
} catch (Exception e) {
if (some_condition(e))
throw RETRY_CONSTANT;
if (other_condition(e))
throw REPORT_FAILURE_CONSTANT | some_useful_bits(e);
}
}
} catch (std::bad_alloc) {
do_something_to_free_some_memory();
continue;
} catch (IntegerException e) {
if (e & REPORT_FAILURE_CONSTANT) {
<extract and record bits from e encoded via some_useful_bits>;
break;
} else {
backoff_if_appropriate();
}
}
}

No doubt I got half a dozen things wrong, but hopefully you get the
general idea that, even with the fairly severe restrictions imposed by
Victor's (not completely) minimal proposal, people can at least do
*something* with exceptions and canceled transactions. This means they
can yell at us, show us their use cases, etc., and we can get some
decent feedback about how people want to use these features.

If we simply shut them down and say they can't do anything of the sort,
we won't get people trying it at all, so we won't learn what they'd like
to do and what they need in order to achieve it. Furthermore, we won't
have a foundation on which to build candidate specifications that
eventually will support exceptions escaping from canceled transactions.
That last part is important IMO, as we have various areas in which we
can and should make progress (e.g. safe by default) that we have not
made progress on because we've spent (too much?) time trying to make
more substantial progress on exceptions and cancellation. While we've
learned a lot, and new ideas are percolating, I think it's probably a
good choice to settle on "minimal progress" in the area of exceptions
that escape canceled transactions in order to facilitate more
substantial progress in other areas, in parallel with the ongoing
discussion of exceptions.

Regarding HTM, while I definitely understand where you're coming from
with "I only want it when it can exploit HTM", it has been all but
forbidden to even discuss HTM in this context until quite recently, and
we are certainly aiming to specify features that can be implemented
without HTM. Even though the HTM landscape has changed considerably and
looks promising, I think we should continue to assume the features we
propose should be effectively implementable without HTM, even though
it's good that we're now at least considering an HTM-enabled future in
these discussions.

Cheers

Mark



>
> I read recently that Haswell's STM support is going to be used as a
> product differentiator, so it won't be enabled in lower end chips. That
> means in practice that hardware assisted STM won't really become
> available till after Haswell, and by which stage I'd assume ARM will
> have added their assist which could be quite different.
>
> I am therefore even more cautious and conservative than I was before
> because my ignorance level has just jumped by an order or two. I feel
> extremely uncertain that now is the time for STM in C++. Let me put this
> another way: I think C++ STM should take the highly successful OpenMP
> approach: implement a just sufficient #pragma based subset for C first,
> and we'll go from there (see
> http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3530.pdf for
> the proposed integration of OpenMP syntax into C++).
>
> That said I'll be at C++ Now 2013, and will attend the talk on C++ STM
> there. I can be convinced I'm being too paranoid.
>
> Niall
>

Niall Douglas

unread,
Apr 3, 2013, 11:22:59 AM4/3/13
to t...@isocpp.org
On Monday, April 1, 2013 8:19:58 PM UTC-4, Mark Moir wrote:

We've probably used the word "minimal" somewhat inadvisedly here.  You
are right that the "small and simple" proposal Victor has made is not
technical "minimal".

Sure, and that's what worries me.
 
This means they
can yell at us, show us their use cases, etc., and we can get some
decent feedback about how people want to use these features.

If we simply shut them down and say they can't do anything of the sort,
we won't get people trying it at all, so we won't learn what they'd like
to do and what they need in order to achieve it.

It's just that it comes across as really "big". Maybe that's unavoidable, but I have the feeling it's because it needs to work without hardware assist, and that's where the complexity is coming in.

What I'm thinking instead is something a bit different, and far, far more minimal (see below).
 
Regarding HTM, while I definitely understand where you're coming from
with "I only want it when it can exploit HTM", it has been all but
forbidden to even discuss HTM in this context until quite recently, and
we are certainly aiming to specify features that can be implemented
without HTM.  Even though the HTM landscape has changed considerably and
looks promising, I think we should continue to assume the features we
propose should be effectively implementable without HTM, even though
it's good that we're now at least considering an HTM-enabled future in
these discussions.


Ok, let me put this another way: Intel's TSX is rather like C's setjmp() (_xbegin()) and longjmp() (_xabort()) but more muted in its effects, so where setjmp/longjmp clobber main memory state but preserve registers (apart from eax), _xbegin/_xabort do *not* clobber main memory and also preserve registers (apart from eax).

Yet the reasons why TSX is bad for C++ are the same as why setjmp/longjmp is bad for C++: C++ has critical stack based state i.e. stack state which ought to have something done with it, and by *not* clobbering registers you throw away that stack state.

The conventional way out here is to use exception throws to correctly unwind state back to the setjmp() (or "try" in C++ parlance). But you can't do this in transactions, because you want *some* stack unwinds to occur but *not* other stack unwinds. In other words, there is effectively two sets of stack unwinds, the set you always want to happen, and the set you want to happen "if".

Where I'm coming from is an ignorance of how best to implement that latter part. Traditionally in my own code I use little RAII lambdas which keep a boolean as whether to execute or not during unwind which effectively implements the dual stack unwind with one stack being conditional, and I find these extremely useful. But I can see that these are a bodge of sorts: what we really need is more than one unwind stack.

In other words, what I'm saying is that I have a gut feeling that the "data storage" stack needs detaching from the "execution unwind state" stack, and the latter needs to be placed under (optional) explicit programmer control. A bit like varargs in C where magic macros let you grok through unknown parameters. That sort of thing. The point being made is that instead of full fat memory transactions, even a "minimal" implementation thereof, I would much rather see explicit control of the C++ compiler's stack unwind mechanism made available to the programmer.

Then, you see, implementing transactions becomes mostly a library issue and we no longer have to fiddle with the language. And we kill many other birds with one stone.

For example, this latter idea also solves the problem of POSIX thread cancellation which is currently incompatible with C++. I'd really like that fixed, it annoys me because cancellation is useful. It also makes a number of areas in Boost.Python, and anywhere else where interpreted languages meet C++, vastly easier to correctly implement.

And I can stop using my RAII lambda bodge in favour of a proper implementation, as I've noticed my colleagues don't like seeing lambdas strewn all over the place.

Does this make sense?

Niall

Mark Moir

unread,
Apr 3, 2013, 4:30:35 PM4/3/13
to t...@isocpp.org


On 4/4/13 4:22 AM, Niall Douglas wrote:
> On Monday, April 1, 2013 8:19:58 PM UTC-4, Mark Moir wrote:
>
>
> We've probably used the word "minimal" somewhat inadvisedly here. You
> are right that the "small and simple" proposal Victor has made is not
> technical "minimal".
>
>
> Sure, and that's what worries me.

Well, if we want to literally do the minimal thing, we can do nothing
and be done. There may be some who think that's the right thing to do,
and they've already (not) done it :-). But that is not the position of
this group. So let's not get hung up on the word "minimal", which as
I've mentioned was not quite what we meant to say anyway.

>
> This means they
> can yell at us, show us their use cases, etc., and we can get some
> decent feedback about how people want to use these features.
>
> If we simply shut them down and say they can't do anything of the sort,
> we won't get people trying it at all, so we won't learn what they'd
> like
> to do and what they need in order to achieve it.
>
>
> It's just that it comes across as really "big". Maybe that's
> unavoidable, but I have the feeling it's because it needs to work
> without hardware assist, and that's where the complexity is coming in.

I really don't think so. If you can elaborate on what makes you feel
this way, we can discuss it more concretely.

One possibility is that you're imagining a world in which *all*
transactions can be committed using HTM, such that no software
alternative is needed, which in turn might mean that we could avoid the
need to instrument code to be executed within transactions, which might
then mean we would not need any language support (with increasing
emphasis with each use of "might" :-)).

Even in such a world, I'm not convinced about that (perhaps some of
those who are well versed in the "why a library implementation is not
sufficient" will weigh in).

But most importantly, there is no reason to believe that such a world
will *ever* exist. While we have started to hear about implementations
that make progress guarantees for a very limited class of transactions
(yay!), *all* publicly known implementations explicitly reserve the
right to abort (just about) *any* transaction for any reason.

Thus, even when/if the currently known implementations make it into
mainstream availability and use, they will still be a *long* way off
from providing sufficient progress guarantees that no software
alternative is needed.

You might think that simply serializing all transactions that can't be
committed with HTM, thereby again avoiding the need for any instrumented
code, should be sufficient. That's debatable from a performance point
of view. However, from a correctness/usability/composability point of
view, it's not acceptable, because it does not provide a way to cancel
transactions that cannot commit using HTM. This was the subject of
debate in Portland, and I think we have now all agreed that this is not
workable. See Section 1 in the N3589 document we submitted. For a
summary of the reasoning, please see my email "Atomic transactions
should be cancelable” from 27 Oct 2012 on the reflector.
Maybe more sense than I am able to grasp :-), but overall my reaction is
that a) I don't believe that a library-only approach will fly (see
above), and b) you are mostly talking about issues involved with stack
unwinding within transactions that are to be canceled. We spent a lot
of time debating this issue since Portland, and ultimately gave up on it
(for now) in the interests of making progress on other issues. This is
summarised in the N3591 document we submitted.

What we are discussing here is *only* about what happens when an
exception escapes a transaction labeled as cancel_on_commit. This has
nothing to do with the unwinding of the stack. For now, there is
nothing special to say about what happens as an exception propagates up
the stack, except at the point where we really *have to* say something,
which is when it is about to escape from a transaction that will be
canceled.

We are attempting to stake out an intermediate short-term position that
will be easy to specify and implement, but will provide sufficient
functionality that programmers can at least experiment with it to some
degree, admittedly with some pain and inconvenience in some cases, so
that we can get feedback and concrete use cases to help inform future
decisions that will hopefully lead to a more complete and usable feature.

Cheers

Mark

Michael L. Scott

unread,
Apr 3, 2013, 5:24:55 PM4/3/13
to t...@isocpp.org
On Apr 3, 2013, at 4:30 PM, Mark Moir wrote:

> On Monday, April 1, 2013 8:19:58 PM UTC-4, Mark Moir wrote:
>
>> On 4/4/13 4:22 AM, Niall Douglas wrote:
>>
>> [snip]
>>
>> Let me put this another way: I think C++ STM should take the highly
>> successful OpenMP approach: implement a just sufficient #pragma based
>> subset for C first, and we'll go from there (see
>> http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3530.pdf for
>> the proposed integration of OpenMP syntax into C++).
>
> [snip]
>
> ... (perhaps some of those who are well versed in the "why a library
> implementation is not sufficient" will weigh in).

I'm one of the people who once thought that a library implementation
might be sufficient. I learned my lesson the hard way.

As far as I can tell, there are two known styles of API for
library-based STM in C++. We tried them both in my group.

Starting and ending a transaction is easy. We might, for example, say
#pragma TX_BEGIN and #pragma TX_END.

It's much harder to figure out how to instrument loads and stores. One
most obvious strategy is to have tx_read() and tx_write() library
routines -- presumably with templated versions for various types.
Because the programmer must insert these by hand, it is very easy to get
them wrong, and very hard to find the errors. The SPLASH apps were
built this way, and people are _still_ finding bugs in them caused by
missing or incorrect read and write annotations.

An alternative strategy is to use smart pointers to "open" transactional
objects, after which all accesses can be "automatically" instrumented
via accessor routines. While we were initially hopeful about this
approach -- and while I still prefer it to fully manual instrumentation --
it turned out to have a host of limitations. We explored these in a
paper at TRANSACT'07; you can find a copy at
http://www.cs.rochester.edu/u/scott/papers/2007_TRANSACT_RSTM2.pdf
if you want to see the details. Among the most serious problems:

- We couldn't safely escape a transaction with a goto, break, return,
or exception.
- We couldn't safely define nonstatic methods (including
constructors!) for transactional classes, because /this/ wasn't a
smart pointer.
- Because we had to delay storage reclamation in code that might be
rolled back, we couldn't control the timing of destructor calls.
- Non-smart pointer objects (e.g., thread-local variables) would not
revert on abort, leading to subtle bugs (less pervasive than the
SPLASH bugs, but still a signficant problem).
- Ideally, one would like to be able to write code that can be called
in both a transactional and nontransactional context. We were able
to approximate this with templates, but it was _very_ awkward.

Smarter designers than us might be able to cure some of these problems,
but I see no chance of solving them all. After investing a huge amount
of time (and writing some pretty substantial applications), we became
convinced that language integration and compiler support would be
essential.

- Michael

Justin Gottschlich

unread,
Apr 8, 2013, 1:41:17 PM4/8/13
to t...@isocpp.org
Sorry for being so late to chime in, I was on travel all last week.
 
In short, I agree with Michael Scott in that a library approach seems unreasonable for TM. Like the Rochester group, we explored supporting TM in a library and published a few papers about it, such as LCSD'07 and BoostCon'10:
 
 
While our early work (LCSD'07) proposed that such an approach might be feasible, when we began to work on more detailed language integration (BoostCon'10), we realized that while we could handle some of the necessary issues to make transactions "work" in C++, the programming complexity seemed to explode. Things like handling a return in the middle of a transaction seemed unreasonably complex and could be easily missed by the average programmer.
 
Because of this, I have completely reversed my initial position that TM can be support elegantly from a library approach. I now feel, as I believe Michael Scott does, that we need direct language and compiler integration to support TM in a way that is accessible for the average (i.e., non-super hero) programmer.
 
Justin
 
 
 

Niall Douglas

unread,
Apr 8, 2013, 5:25:44 PM4/8/13
to t...@isocpp.org
On Monday, April 8, 2013 1:41:17 PM UTC-4, Justin Gottschlich wrote:
In short, I agree with Michael Scott in that a library approach seems unreasonable for TM. Like the Rochester group, we explored supporting TM in a library and published a few papers about it, such as LCSD'07 and BoostCon'10:
 
 
While our early work (LCSD'07) proposed that such an approach might be feasible, when we began to work on more detailed language integration (BoostCon'10), we realized that while we could handle some of the necessary issues to make transactions "work" in C++, the programming complexity seemed to explode. Things like handling a return in the middle of a transaction seemed unreasonably complex and could be easily missed by the average programmer.

You may have missed what I originally said, which was not "we can implement this as a library".

I said "we should open up stack unwinding to programmer control". That implies changes to the language and especially the runtime. I said that *then* and only then I reckon you could implement a good chunk of memory transactions as a pure-library solution.

One also, as I mentioned, gets to solve several long standing ABI breakages between C and C++. Such as POSIX thread cancellation. You also fix throwing C++ exceptions through an intermediate C layer without that C layer aborting. There was a post on Boost.Python DL just recently about this problem where a guy was getting clobbered by a third party binary blob.

Later on, in the post C++17 world, you also get to solve intra-compiler ABI breakage between C++ Modules. For example, currently GCC compiled C++ would have a hard time interoperating with MSVC compiled C++ because their exception throwing systems are quite different. Think this an abtuse example? Okay, what about STL implementation compatibility? We have three major STL implementations in the wild, and ever increasing headaches from library A which will only work with STL A trying to be used by a program which also needs library B which will only work with STL B.

Right now people are doing craziness like dlload(RTLD_LOCAL) to get incompatible STL implementations to coexist in a process. That's only a viper's nest which will continue to grow :(

My larger point here is that you can work around most ABI issues, but not correct stack unwind semantics without opening it up to programmer control. I probably didn't make sense in my earlier post, but STM is just another longjmp() style ABI breakage issue as I pointed out.

It therefore should be treated as such: solve longjmp() in C++, and you solve STM in C++.

Niall

Torvald Riegel

unread,
Apr 9, 2013, 11:29:03 AM4/9/13
to t...@isocpp.org
On Mon, 2013-04-08 at 14:25 -0700, Niall Douglas wrote:
> Later on, in the post C++17 world, you also get to solve
> intra-compiler ABI breakage between C++ Modules. For example,
> currently GCC compiled C++ would have a hard time interoperating with
> MSVC compiled C++ because their exception throwing systems are quite
> different.

I wouldn't classify incompatibility between different ABIs as "ABI
breakage". Unless the C++ standard wants to specify a single allowed
ABI for each platform/arch combination, we will continue to have several
ABIs being allowed.

How ABI issues are being handled is more a platform-related issue than a
language-related issue. Platforms/vendors deal with this differently,
but there are definitely platforms/vendors that consider ABI stability
as important.

> Think this an abtuse example? Okay, what about STL implementation
> compatibility? We have three major STL implementations in the wild,
> and ever increasing headaches from library A which will only work with
> STL A

That is an API issue, unless you're thinking about compiled code here.

> trying to be used by a program which also needs library B which will
> only work with STL B.

That's an issue that you can get with any other kinds of libraries.
>
> Right now people are doing craziness like dlload(RTLD_LOCAL) to get
> incompatible STL implementations to coexist in a process. That's only
> a viper's nest which will continue to grow :(
>
>
> My larger point here is that you can work around most ABI issues, but
> not correct stack unwind semantics without opening it up to programmer
> control.

But this won't solve all ABI issues that you might have on certain
platforms. Second, given that this isn't a language-spec-related issue,
it's questionable to me why the language should try to offer a solution.

Anyway, this is off-topic wrt. the SG5 list, I think.

> I probably didn't make sense in my earlier post, but STM is just
> another longjmp() style ABI breakage issue as I pointed out.

I disagree. Aside from that I can't see any breakage when a platform
sticks to the ABIs it uses, any TM ABI being used by compiled code is
just part of all the other ABIs being relied on (e.g., calling
conventions, exception handling, ...). Also note that even if you use
an HTM to implement transactions, there will still be an ABI that
consists of more than just using the HTM parts of the ISA of the
particular architecture.

If you're concerned about ABI compatibility between certain compilers, I
suggest encouraging the particular vendors to get together and agree on
a shared ABI.
Intel and Red Hat have worked on a common TM ABI for ICC/GCC (see GCC's
documentation).


Torvald

Niall Douglas

unread,
Apr 10, 2013, 11:00:32 AM4/10/13
to t...@isocpp.org
On Tuesday, April 9, 2013 11:29:03 AM UTC-4, Torvald Riegel wrote:
> Think this an abtuse example? Okay, what about STL implementation
> compatibility? We have three major STL implementations in the wild,
> and ever increasing headaches from library A which will only work with
> STL A

That is an API issue, unless you're thinking about compiled code here.

Ah, but the STL is a very special case. It's the only API specifically endorsed by the standard.

Let me put this another way: you'd reasonably expect to be able to swap one libc for another, or combine a binary built against one version of a libc with a different version. You currently can't expect that with STL implementations: you generally need to recompile.

Which in my mind is an ABI breakage, albeit one mostly language introduced.

You're right this is off-topic, so I'll stop here. My main point is that solving C++ memory transactions in isolation is the wrong approach IMHO. You need to be thinking in terms of "what is the major breakage in C and C++ interop of which memory transactions is but one of many" and now you're asking a much better question. Hence me pointing out longjmp().

> I probably didn't make sense in my earlier post, but STM is just
> another longjmp() style ABI breakage issue as I pointed out.

I disagree.  Aside from that I can't see any breakage when a platform
sticks to the ABIs it uses, any TM ABI being used by compiled code is
just part of all the other ABIs being relied on (e.g., calling
conventions, exception handling, ...).  Also note that even if you use
an HTM to implement transactions, there will still be an ABI that
consists of more than just using the HTM parts of the ISA of the
particular architecture.

I think I still haven't explained myself properly, as it appears you have different understanding of an ABI to me.

What I'm saying is that when memory transactions abort it has a very similar effect on C++'s execution state as longjmp(). Solve longjmp() working well with C++ and you solve memory transactions in C++.

You also solve many, many interrelated longstanding problems and bugs with interop between C and C++. For example, thread cancellation, interop between Python and C++ via Boost.Python, etc etc.

I'll be at C++ Now 2013. Is there scope for some sort of workshop?

Niall

Torvald Riegel

unread,
Apr 10, 2013, 11:50:11 AM4/10/13
to t...@isocpp.org
On Wed, 2013-04-10 at 08:00 -0700, Niall Douglas wrote:
> On Tuesday, April 9, 2013 11:29:03 AM UTC-4, Torvald Riegel wrote:
> > Think this an abtuse example? Okay, what about STL
> implementation
> > compatibility? We have three major STL implementations in
> the wild,
> > and ever increasing headaches from library A which will only
> work with
> > STL A
>
> That is an API issue, unless you're thinking about compiled
> code here.
>
>
> Ah, but the STL is a very special case. It's the only API specifically
> endorsed by the standard.

That's true, and stdlib implementations should really be compatible with
what the standard requires.
>
> Let me put this another way: you'd reasonably expect to be able to
> swap one libc for another

No, not in the general case, unless they guarantee to use the same ABI.

> , or combine a binary built against one version of a libc with a
> different version.

Symbol versioning can help here, but that's more a way to make sure that
a library can support more than one version of an interface.

> You currently can't expect that with STL implementations: you
> generally need to recompile.

In general, that's certainly the case; but if the STL implementations
you have in mind pay attention to using a stable ABI, you shouldn't have
to recompile.
>
> Which in my mind is an ABI breakage, albeit one mostly language
> introduced.
>
>
> You're right this is off-topic, so I'll stop here. My main point is
> that solving C++ memory transactions in isolation is the wrong
> approach IMHO.

I can't speak for other implementors, but for GCC, we did pay attention
to having the TM implementation work well with all the other parts.
Note that this is in most parts orthogonal to whether we provide
transactions as a language or library feature.

> You need to be thinking in terms of "what is the major breakage in C
> and C++ interop of which memory transactions is but one of many"

I don't see how memory transactions would break in a mixed C/C++
program. Exceptions can be as difficult as without transactions, so
that's not inherent to the transactions. Perhaps you could look at the
ABI and point out the issues you see in detail?

> and now you're asking a much better question. Hence me pointing out
> longjmp().

There are differences to longjmp, in particular that you can't just jump
some place else on abort/cancellation/..., but to exactly one of the
enclosing transactions. If you think about how to implement TM in
compilers, I think you will see that the toolchain has all the code
under control and analyzed.
>
> > I probably didn't make sense in my earlier post, but STM is
> just
> > another longjmp() style ABI breakage issue as I pointed
> out.
>
> I disagree. Aside from that I can't see any breakage when a
> platform
> sticks to the ABIs it uses, any TM ABI being used by compiled
> code is
> just part of all the other ABIs being relied on (e.g.,
> calling
> conventions, exception handling, ...). Also note that even if
> you use
> an HTM to implement transactions, there will still be an ABI
> that
> consists of more than just using the HTM parts of the ISA of
> the
> particular architecture.
>
>
> I think I still haven't explained myself properly, as it appears you
> have different understanding of an ABI to me.

So what's your understanding of an ABI?
>
> What I'm saying is that when memory transactions abort it has a very
> similar effect on C++'s execution state as longjmp().

No. In particular, there's no guarantee of all-or-nothing with longjmp.
An abort+restart of a transaction is invisible to the program (as it
should be). Cancellation is visible, but in the sense that something
else gets executed *instead of* the transaction.

> Solve longjmp() working well with C++ and you solve memory
> transactions in C++.
>
>
> You also solve many, many interrelated longstanding problems and bugs
> with interop between C and C++. For example, thread cancellation,
> interop between Python and C++ via Boost.Python, etc etc.
>
>
> I'll be at C++ Now 2013. Is there scope for some sort of workshop?

I won't be there. Perhaps others in SG5 will be.


Niall Douglas

unread,
Apr 11, 2013, 11:35:58 AM4/11/13
to t...@isocpp.org

> What I'm saying is that when memory transactions abort it has a very
> similar effect on C++'s execution state as longjmp().

No.  In particular, there's no guarantee of all-or-nothing with longjmp.
An abort+restart of a transaction is invisible to the program (as it
should be).  Cancellation is visible, but in the sense that something
else gets executed *instead of* the transaction.

I think that sums up exactly how you're missing my point entirely. It's a wood vs trees thing.

Niall

Mark Moir

unread,
Apr 11, 2013, 10:14:09 PM4/11/13
to t...@isocpp.org

Hi Niall

FWIW, I think I am missing your point too. Saying it's a "wood vs
trees" thing doesn't get me closer to getting your point. But let me
explain one place where I've felt a significant disconnect in this
discussion, in case it helps clarify how we're not understanding you.

You've said a couple of times something like "solve longjmp() in C++,
and you solve STM in C++". Torvald's responses to you have mostly made
sense to me, which suggests that perhaps we're both misunderstanding you
in similar ways.

My reactions to your sentence quoted above are:

0) longjmp is just about behavior of a single thread: control flow and
thread-private state. TM is about multithreaded behavior and shared
state. So no amount of "solving" longjmp will "solve" STM. But, ...

1) ... maybe I don't know what you mean by "solve STM".

Maybe what you mean is that solving the issues you have in mind for
longjmp will solve the specific issues related to control flow and
thread-private state for transactions. If so, understanding this might
help us past a disconnect or two and allow a more fruitful discussion of
what you're thinking.

Please feel free to ignore this if it's not helpful. I hope it is.

Cheers

Mark

Bronek Kozicki

unread,
Apr 14, 2013, 8:23:47 AM4/14/13
to t...@isocpp.org
FWIW, I very much agree with you here. To be more specific : the point of transactional memory is extending the capabilities of multithreaded programming without imposing kernel level thread synchronization. Currently to this effect, C++ has only <atomic> . Primitives defined in this header come with one limitation which can only be addressed with transactional memory : it is inability to perform indivisible memory operation on more than one location at the same time. Let us focus on giving this ability to C++ , rather than dwell in side issues. To do it well we need to make it useful to programmers in the first place, by ensuring that the language can make use of hardware support when available, thus providing performance benefits expected of TM. We must also consider cooperation of this feature with existing C++ strengths. Personally, I support the opinion that from the point of view of program execution flow, memory transaction should be always either fully executed, or not executed at all without any side effects. This makes it very much unlike exception handling, so should be best addressed as orthogonal.

We do not need, nor should we try to, address old limitations of C++ such as ABI incompatibilities or difference in program behaviour or semantics compared to C.  This is not to say that these are non-issues, but rather that these should not play in our consideration of TM in C++ .

B.


From: "Torvald Riegel" <tri...@redhat.com>
To: "t...@isocpp.org" <t...@isocpp.org>
Sent: 10 April 2013 16:50
Subject: Re: [tm] Re: "Minimalist" proposal for exceptions escaping from a cancelled transaction

On Wed, 2013-04-10 at 08:00 -0700, Niall Douglas wrote:
> On Tuesday, April 9, 2013 11:29:03 AM UTC-4, Torvald Riegel wrote:
>         > Think this an abtuse example? Okay, what about STL
>         implementation 
>         > compatibility? We have three major STL implementations in
>         the wild, 
>         > and ever increasing headaches from library A which will only
>         work with 
>         > STL A 
>         
>         That is an API issue, unless you're thinking about compiled
>         code here. 
> 
> 
> Ah, but the STL is a very special case. It's the only API specifically
> endorsed by the standard.

That's true, and stdlib implementations should really be compatible with
what the standard requires.
> 
> Let me put this another way: you'd reasonably expect to be able to
> swap one libc for another

No, not in the general case, unless they guarantee to use the same ABI.

> , or combine a binary built against one version of a libc with a
> different version.

Symbol versioning can help here, but that's more a way to make sure that
a library can support more than one version of an interface.

> You currently can't expect that with STL implementations: you
> generally need to recompile.

In general, that's certainly the case; but if the STL implementations
you have in mind pay attention to using a stable ABI, you shouldn't have
to recompile.
> 
> Which in my mind is an ABI breakage, albeit one mostly language
> introduced.
> 
> 
> You're right this is off-topic, so I'll stop here. My main point is
> that solving C++ memory transactions in isolation is the wrong
> approach IMHO.

I can't speak for other implementors, but for GCC, we did pay attention
to having the TM implementation work well with all the other parts.
Note that this is in most parts orthogonal to whether we provide
transactions as a language or library feature.

> You need to be thinking in terms of "what is the major breakage in C
> and C++ interop of which memory transactions is but one of many"

I don't see how memory transactions would break in a mixed C/C++
program.  Exceptions can be as difficult as without transactions, so
that's not inherent to the transactions.  Perhaps you could look at the
ABI and point out the issues you see in detail?

> and now you're asking a much better question. Hence me pointing out
> longjmp().

There are differences to longjmp, in particular that you can't just jump
some place else on abort/cancellation/..., but to exactly one of the
enclosing transactions.  If you think about how to implement TM in
compilers, I think you will see that the toolchain has all the code
under control and analyzed.
> 
>         > I probably didn't make sense in my earlier post, but STM is
>         just 
>         > another longjmp() style ABI breakage issue as I pointed
>         out. 
>         
>         I disagree.  Aside from that I can't see any breakage when a
>         platform 
>         sticks to the ABIs it uses, any TM ABI being used by compiled
>         code is 
>         just part of all the other ABIs being relied on (e.g.,
>         calling 
>         conventions, exception handling, ...).  Also note that even if
>         you use 
>         an HTM to implement transactions, there will still be an ABI
>         that 
>         consists of more than just using the HTM parts of the ISA of
>         the 
>         particular architecture. 
> 
> 
> I think I still haven't explained myself properly, as it appears you
> have different understanding of an ABI to me.

So what's your understanding of an ABI?
> 
> What I'm saying is that when memory transactions abort it has a very
> similar effect on C++'s execution state as longjmp().

No.  In particular, there's no guarantee of all-or-nothing with longjmp.
An abort+restart of a transaction is invisible to the program (as it
should be).  Cancellation is visible, but in the sense that something
else gets executed *instead of* the transaction.

> Solve longjmp() working well with C++ and you solve memory
> transactions in C++.
> 
> 
> You also solve many, many interrelated longstanding problems and bugs
> with interop between C and C++. For example, thread cancellation,
> interop between Python and C++ via Boost.Python, etc etc.
> 
> 
> I'll be at C++ Now 2013. Is there scope for some sort of workshop?

I won't be there.  Perhaps others in SG5 will be.


-- 
You received this message because you are subscribed to the Google Groups "SG5 - Transactional Memory" group.
To unsubscribe from this group and stop receiving emails from it, send an email to tm+unsu...@isocpp.org.
To post to this group, send email to t...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/tm/?hl=en.


Niall Douglas

unread,
Apr 15, 2013, 1:06:28 PM4/15/13
to t...@isocpp.org

On Thursday, April 11, 2013 10:14:09 PM UTC-4, Mark Moir wrote:

FWIW, I think I am missing your point too.  Saying it's a "wood vs
trees" thing doesn't get me closer to getting your point.  But let me
explain one place where I've felt a significant disconnect in this
discussion, in case it helps clarify how we're not understanding you.

One is always never sure if anyone is listening and therefore whether you ought to bother. 95% of email I send doesn't get read fully let alone anyone taking my opinion seriously. That's pretty normal in any corporation of course, but I need to know whether to focus elsewhere.
 
You've said a couple of times something like "solve longjmp() in C++,
and you solve STM in C++".  Torvald's responses to you have mostly made
sense to me, which suggests that perhaps we're both misunderstanding you
in similar ways.

My reactions to your sentence quoted above are:

0) longjmp is just about behavior of a single thread: control flow and
thread-private state.  TM is about multithreaded behavior and shared
state.  So no amount of "solving" longjmp will "solve" STM.  But, ...

1) ... maybe I don't know what you mean by "solve STM".

Maybe what you mean is that solving the issues you have in mind for
longjmp will solve the specific issues related to control flow and
thread-private state for transactions.  If so, understanding this might
help us past a disconnect or two and allow a more fruitful discussion of
what you're thinking.

That's exactly one half of what I mean. You make a very good point, and moreover it's useful as it shows how I've failed to communicate.

For me STM is entirely about flow control, because that's the hard part and the part we have systematically avoided resolving for over a decade precisely because it's hard. The STM memory model is quite straightforward in comparison: harder than Hans' acquire/release atomics, but still straightforward.

Moreover, the real value of STM outside use as a CAS lock optimisation stems almost entirely from flow control, because it enhances C++'s existing flow control in even more useful (and breaking of existing code idioms) ways. To explain, C++'s main innovation in flow control over C is the ability to undo the stack i.e. RAII objects. That part is uncontroversial, except for the breakage with some C idioms like signal handling and longjmp which we decided at the time was worth it. With the introduction of C++98, you can also, at any time, say "start undoing the stack immediately" aka throwing an exception. Yet that latter innovation is also C++'s primary incompatibility with C, and therefore is C++'s primary incompatibility with itself. As a result here at BlackBerry for example, much as in many of the tech multinationals, we have a significant developer mindshare which believe that C++ exceptions should be universally disabled because then you don't have to bother with Abrahams' exception safety guarantees i.e. "we don't have to bother retrofitting our large C++ codebase to be exception safe". As a result, many, if not most, large C++ libraries aren't exception safe e.g. Qt and you therefore can't safely mix ANY exception unsafe C++ with exception safe C++ e.g. the STL. That situation has resulted entirely because no one has grasped the nettle about solving, once and for all, the flow control interop between C style "forward stack only with gotos" programming, pre-C++98 "forward stack only and no gotos" and post-C++98 style "forward and reversible stack and no gotos" programming. And we should, because I'm sick and tired of dealing with bug reports caused by the STL throwing exceptions inside Qt code which instantly aborts the process etc. etc. which any C++ programmer has the right to expect to "just work".

Judging from the mention of past disagreement about what to do about flow control in STM, these old bugs are rearing their heads again. These aren't STM-specific, despite that many on this list seem to think they are. The truth is we, as a community, never finished integrating C++ type flow control into the language. And we ought to, because it also solves full fat STM incorporation into C++. And moreover, and why I raise all this right now, if achieved I'm almost certain that your ideal STM implementation would suddenly look a lot different i.e. much simpler to implement from now.

In other words, this study group appears to be getting bogged down by trying to solve full fat STM in C++ in isolation of other language issues. I'm saying I think you should pivot.
 
Please feel free to ignore this if it's not helpful.  I hope it is.
 
I run into these problems regularly for over a decade, and they are getting exponentially worse as C++11 comes online e.g. just now I'm working with some code which uses asynchronous signals to notify a process of completion, something which does not fit easily with Boost.ASIO, parts of which are expected to enter with TR2. Fixing them permanently would be a huge productivity aid.

So long as I'm not wasting my time I am happy to engage. Thanks for reaching out Mark.

Niall

Jens Maurer

unread,
Apr 15, 2013, 3:22:28 PM4/15/13
to t...@isocpp.org
On 04/15/2013 07:06 PM, Niall Douglas wrote:
> For me STM is entirely about flow control, because that's the hard
> part and the part we have systematically avoided resolving for over a
> decade precisely because it's hard. The STM memory model is quite
> straightforward in comparison: harder than Hans' acquire/release
> atomics, but still straightforward.

As far as I understand, we're trying to do transactional memory in
this group, so I'm slightly confused by your use of the term "STM",
which (as far as I know) stands for "software transactional memory",
one of possibly several implementation options for transactional memory.
I'll read "STM" as "TM" for the rest of your e-mail.

> Moreover, the real value of STM outside use as a CAS lock
> optimisation stems almost entirely from flow control, because it
> enhances C++'s existing flow control in even more useful (and
> breaking of existing code idioms) ways.

Do you subsume the ability to compare-and-swap different, unrelated
memory locations atomically under "CAS lock optimization"? For me,
this "multi-location atomicity cheaper than locks on average" is
a major feature.

[Retrofitting exception safety requires changing existing code.]
> As
> a result, many, if not most, large C++ libraries aren't exception
> safe e.g. Qt and you therefore can't safely mix ANY exception unsafe
> C++ with exception safe C++ e.g. the STL. That situation has resulted
> entirely because no one has grasped the nettle about solving, once
> and for all, the flow control interop between C style "forward stack
> only with gotos" programming, pre-C++98 "forward stack only and no
> gotos" and post-C++98 style "forward and reversible stack and no
> gotos" programming. And we should, because I'm sick and tired of
> dealing with bug reports caused by the STL throwing exceptions inside
> Qt code which instantly aborts the process etc. etc. which any C++
> programmer has the right to expect to "just work".

Do you have any specific suggestion to help the integration of
exception-safe and unsafe code?

TM can help here, in a way, because it can undo memory effects
of otherwise exception-unsafe code when an exception escapes it
(cancel-on-exception-escape). However, in order to provide this
ability, TM comes with a set of requirements for the contained code.
(For example, no I/O is allowed.) I doubt that something as large
as Qt satisfies these requirements.

> Judging from the mention of past disagreement about what to do about
> flow control in STM, these old bugs are rearing their heads again.
> These aren't STM-specific, despite that many on this list seem to
> think they are. The truth is we, as a community, never finished
> integrating C++ type flow control into the language. And we ought to,
> because it also solves full fat STM incorporation into C++.

Do you have any specific suggestions?

> And
> moreover, and why I raise all this right now, if achieved I'm almost
> certain that your ideal STM implementation would suddenly look a lot
> different i.e. much simpler to implement from now.

Maybe.

> In other words, this study group appears to be getting bogged down by
> trying to solve full fat STM in C++ in isolation of other language
> issues. I'm saying I think you should pivot.

This TM group has a lot of TM experts, but the majority of C++ language
experts is probably elsewhere. I'm not sure what your expectation
is here.

> I run into these problems regularly for over a decade, and they are
> getting exponentially worse as C++11 comes online e.g. just now I'm
> working with some code which uses asynchronous signals to notify a
> process of completion, something which does not fit easily with
> Boost.ASIO, parts of which are expected to enter with TR2. Fixing
> them permanently would be a huge productivity aid.

I don't see how issues with the integration of asynchronous signals
with Boost.ASIO is in any way related to TM, other than both struggling
with flow control, albeit at different levels.

Thanks,
Jens

Mark Moir

unread,
Apr 15, 2013, 8:22:25 PM4/15/13
to t...@isocpp.org

Hi Niall

The response Jens already sent says much of what I would say, and he is
much more expert in C++ issues than I am and is in a better position to
comment. So I'll just add a couple of comments below.
I don't agree with this at all. We've had a lot of discussion recently
on issues related to integrating transactions with exceptions. But this
is after quite a lot of hard work on many aspects related to
concurrency, which is in my mind by far the most important aspect of
transactions. Even if I think about the benefits of transactions in
single-threaded code, I still think first of the data-related aspects
(e.g., ability to undo effects of a transaction without writing explicit
code for this purpose), and not about control flow.

I'm not saying that C++ is already perfect wrt control flow. If there
were a parallel or subsequent effort to improve C++ control flow, that
might enable and/or necessitate changes to how transactions are
integrated, but it is not in the charter of this group, nor is it in the
areas of expertise of most participants in this group, to "fix" C++
control flow.


>
> Moreover, the real value of STM outside use as a CAS lock optimisation
> stems almost entirely from flow control, because it enhances C++'s
> existing flow control in even more useful (and breaking of existing code
> idioms) ways. To explain, C++'s main innovation in flow control over C
> is the ability to undo the stack i.e. RAII objects. That part is
> uncontroversial, except for the breakage with some C idioms like signal
> handling and longjmp which we decided at the time was worth it. With the
> introduction of C++98, you can also, at any time, say "start undoing the
> stack immediately" aka throwing an exception. Yet that latter innovation
> is also C++'s primary incompatibility with C, and/therefore is C++'s
> primary incompatibility with *itself*/. As a result here at BlackBerry
> for example, much as in many of the tech multinationals, we have a
> significant developer mindshare which believe that C++ exceptions should
> be universally disabled because then you don't have to bother with
> Abrahams' exception safety guarantees i.e. "we don't have to bother
> retrofitting our large C++ codebase to be exception safe". As a result,
> many, if not most, large C++ libraries aren't exception safe e.g. Qt and
> you therefore can't safely mix ANY exception unsafe C++ with exception
> safe C++ e.g. the STL. That situation has resulted entirely because no
> one has grasped the nettle about solving, once and for all, the flow
> control interop between C style "forward stack only with gotos"
> programming, pre-C++98 "forward stack only and no gotos" and post-C++98
> style "forward and reversible stack and no gotos" programming. And we
> should, because I'm sick and tired of dealing with bug reports caused by
> the STL throwing exceptions inside Qt code which instantly aborts the
> process etc. etc. which any C++ programmer has the right to expect to
> "just work".
>
> Judging from the mention of past disagreement about what to do about
> flow control in STM,these old bugs are rearing their heads again. These
> aren't STM-specific, despite that many on this list seem to think they
> are. The truth is we, as a community, never finished integrating C++
> type flow control into the language. And we ought to, because it also
> solves full fat STM incorporation into C++. And moreover, and why I
> raise all this right now, if achieved I'm almost certain that your ideal
> STM implementation would suddenly look a lot different i.e. much simpler
> to implement from now.

I really don't think so. There are a number of fairly difficult
implementation and specification issues related to reasonable language
integration and concurrency that have nothing whatsoever to do with
explicit cancellation (which is the source of most if not all of any
disagreement related in any way to control flow). None of those will
magically disappear if someone "fixes control flow".

Cheers

Mark

>
> In other words, this study group appears to be getting bogged down by
> trying to solve full fat STM in C++ in isolation of other language
> issues. I'm saying I think you should pivot.
>
> Please feel free to ignore this if it's not helpful. I hope it is.
>
> I run into these problems regularly for over a decade, and they are
> getting exponentially worse as C++11 comes online e.g. just now I'm
> working with some code which uses asynchronous signals to notify a
> process of completion, something which does not fit easily with
> Boost.ASIO, parts of which are expected to enter with TR2. Fixing them
> permanently would be a huge productivity aid.
>
> So long as I'm not wasting my time I am happy to engage. Thanks for
> reaching out Mark.
>
> Niall
>

Niall Douglas

unread,
Apr 16, 2013, 10:57:26 AM4/16/13
to t...@isocpp.org
On Monday, April 15, 2013 3:22:28 PM UTC-4, Jens Maurer wrote:
 
Do you subsume the ability to compare-and-swap different, unrelated
memory locations atomically under "CAS lock optimization"?  For me,
this "multi-location atomicity cheaper than locks on average" is
a major feature.

No, that's a new method of flow control. And a breaking one, because no exact equivalent can exist without it. In particular I can see recursive mutexs in particular being much less necessary with multi-CAS under TM.
 
[Retrofitting exception safety requires changing existing code.]
>  As
> a result, many, if not most, large C++ libraries aren't exception
> safe e.g. Qt and you therefore can't safely mix ANY exception unsafe
> C++ with exception safe C++ e.g. the STL. That situation has resulted
> entirely because no one has grasped the nettle about solving, once
> and for all, the flow control interop between C style "forward stack
> only with gotos" programming, pre-C++98 "forward stack only and no
> gotos" and post-C++98 style "forward and reversible stack and no
> gotos" programming. And we should, because I'm sick and tired of
> dealing with bug reports caused by the STL throwing exceptions inside
> Qt code which instantly aborts the process etc. etc. which any C++
> programmer has the right to expect to "just work".

Do you have any specific suggestion to help the integration of
exception-safe and unsafe code?

I stress I haven't thought about this list in depth, but as a beginning:

1. You should be able to mark up each function with its control flow style, so I mentioned C, pre98C++, and post98C++. As I mentioned above, control flow styles are somewhat incompatible with one another, and right now the compiler's symbol resolution machinery cannot distinguish between symbols except to say "this one is mangled" and "this one is not". It therefore cannot spot when exception safe code is about to lose exception safety by called an exception unsafe function, and issue a warning/refuse to compile. Think of this like noexcept, so by this I imply mangling changes.

2. Furthermore, if a function declares itself as exception safe, the compiler should be able to verify it as definitely so. This implies some language changes. I'm not good at language design, so I'll leave this bit to someone more competent. But I can see that LLVM bytecode could be instrumented with sufficient information that a link time code generator could spot non-obvious loss of exception safety.

3. Enable the programmer to directly control the C++ execution stack, and to enable multiple C++ execution stacks. We actually already have enabled multiple C++ execution stacks in C++11 via the non-capturing lambda, but they can't be directly controlled, and they are inherently temporary without Boost.Coroutine et al tricks.

4. Enable the programmer to inspect a C++ execution stack, and to modify it. By modify, I really mean "make a copy" because C++ execution stacks are inherently const static, but my point is you should be able to make a copy different to an existing one, and replace the existing one as the from-now-on current one such that as the stack unwinds a different set of things happen than before.


Tentative 5. As I mentioned early on, I feel myself too ignorant to say much about TM. However, I suspect that full fat TMed C++ is going to become a new idiom of C++ flow control. That would mean there would now be four types of C++ flow control, C (one forward stack, gotos), pre98C++ (one forward stack, no gotos), post98C++ (one forward and reversible stack, no gotos), post17C++ (many forward and reversible stacks, gotos).

Therefore everything I just outlined about exception safety management above also applies to solving full fat C++ TM.
 
TM can help here, in a way, because it can undo memory effects
of otherwise exception-unsafe code when an exception escapes it
(cancel-on-exception-escape).  However, in order to provide this
ability, TM comes with a set of requirements for the contained code.
(For example, no I/O is allowed.)  I doubt that something as large
as Qt satisfies these requirements.

What I just proposed does allow i/o to be reversed if a transaction fails. That's where the ability to inspect and rewrite the execution stack comes in very handy. Note I'm not claiming that reversing an i/o in this circumstance would be performant, in fact I'd imagine it would have terrible performance, much as exception throwing right now can have terrible corner case performance too. In other words, you shouldn't do i/o inside a TM if you want performance, but equally you don't want a terminate() if some third party code just happens to do so rarely.

Niall

Niall Douglas

unread,
Apr 17, 2013, 10:54:25 AM4/17/13
to t...@isocpp.org

On Monday, April 15, 2013 8:22:25 PM UTC-4, Mark Moir wrote:

> For me STM is entirely about flow control, because that's the hard part
> and the part we have systematically avoided resolving for over a decade
> precisely because it's hard. The STM memory model is quite
> straightforward in comparison: harder than Hans' acquire/release
> atomics, but still straightforward.

I don't agree with this at all.  We've had a lot of discussion recently
on issues related to integrating transactions with exceptions.  But this
is after quite a lot of hard work on many aspects related to
concurrency, which is in my mind by far the most important aspect of
transactions.  Even if I think about the benefits of transactions in
single-threaded code, I still think first of the data-related aspects
(e.g., ability to undo effects of a transaction without writing explicit
code for this purpose), and not about control flow.

I think the reason we dimorph on this point is that I treat memory in the macro-sense, so I see the L1 cache as differing from cloud storage only by difficulty (really latency) of accessing it.

Don't get me wrong: it's not a critique. Just a technique and style of approach (my time "away" from software and tech 2004-2011 has changed me).
 
I'm not saying that C++ is already perfect wrt control flow.  If there
were a parallel or subsequent effort to improve C++ control flow, that
might enable and/or necessitate changes to how transactions are
integrated, but it is not in the charter of this group, nor is it in the
areas of expertise of most participants in this group, to "fix" C++
control flow.

You'd be surprised though at how much the SG's interrelate. As I reckon this thread has probably reached a useful conclusion at this point, I'll just quickly explain what I'm up to so you know. I have a side project which I do for two hours after work each day which aims to implement a MVCC transactional component object model for C++, so this would mostly extend SG2 C++ Modules and SG4 Networking (via Boost.ASIO), but it touches heavily on this SG5, SG7 Reflection, and the forthcoming SG11 Databases. Hopefully first code might hit Boost before the end of 2013, maybe early 2014.
 
I really don't think so.  There are a number of fairly difficult
implementation and specification issues related to reasonable language
integration and concurrency that have nothing whatsoever to do with
explicit cancellation (which is the source of most if not all of any
disagreement related in any way to control flow).  None of those will
magically disappear if someone "fixes control flow".
 
No one is claiming that anything magically disappears, so sorry if I gave that impression. What I meant was that with debugged flow control markup, it becomes much /easier/ to solve your TM problems. Right now they're wicked hard, perhaps insolubly so. Chip away at the foundations long enough though and it'll eventually tip over, but it'll take you far away from your comfort zone I'm sure (like any good wicked hard problem always does).

Niall

Reply all
Reply to author
Forward
0 new messages