Date: Thu, 09 Jan 2020 16:04:29 -0500
Regarding what is allowed within transactions, our current document (P1875R0) says:
An implementation must specify which kinds of operations are supported (permitted to occur) in the dynamic extent of a transaction. At a minimum, this must include: ordinary (non-atomic, non-volatile) reads and writes, ordinary (non-exceptional) control flow, and calls to constexpr functions (even if their arguments are not constants), and to functions defined in the current compilation unit whose bodies would themselves be permissible inside an atomic block. ... [T]he behavior of a program that executes an unsupported operation within the dynamic scope of a transaction is undefined.
I’m questioning the need to restrict the guarantee down to local and constexpr functions. Note that even the current language permits non-transaction-safe operations in the static scope of the transaction. This is a deliberate choice, designed to accommodate code that never executes those operations:
if (foo) {
atomic {
...
if (!foo) {
cout << “oops”;
}
}
}
Given the possibility that the elided code might change foo, the compiler can’t know that this is safe.
Calls to benign external functions (functions that would be transaction safe if included in the current compilation unit) are clearly no obstacle to correct execution by HTM. They are problematic for fast execution by STM, because the difficulty of obtaining a properly instrumented copy of the function’s code may prompt the compiler to fall back to a global lock. But the lock is indeed a correct implementation, and the STM implementation must be prepared to fall back to it in any case, given the possibility of recurring dynamic conflicts.
In short, the only reason I can think of why an implementation might want to preclude calls to external functions is to avoid the need to check for the existence such calls. That’s a very small burden, and it needs to be balanced against (1) the convenience to programmers of knowing that external calls will be supported by all conforming implementations and (2) the reduction in complexity of the technical spec if we simply say that
An implementation must specify which kinds of operations are supported (permitted to occur) in the dynamic extent of a transaction. At a minimum, this must include: ordinary (non-atomic, non-volatile) reads and writes, and ordinary (non-exceptional) control flow and function calls. ... [T]he behavior of a program that executes an unsupported operation within the dynamic scope of a transaction is undefined.
- Michael
>> On Jan 9, 2020, at 1:01 PM, Hans Boehm via SG5 <sg5_at_[hidden]> wrote:
>>
>> On Mon, Jan 6, 2020 at 7:01 PM Tim Sweeney <tim.sweeney_at_[hidden]> wrote:
>>
>> My two cents: Drop the constexpr or other wording about what functions are to be transaction safe, and say an implementation must ensure that any concurrent execution of an atomic block with defined behavior must be observably equivalent to taking a global lock.
>>
>> Today that works for:
>> - a software global lock
>> - HTM implementations that fall back to a global lock
>>
>> In the future, this would also work for more thorough compiler-driven software implementations of transactional memory that abort and take a global lock if any non-transaction-safe library function is called, like I/O, or any extern function is called that may have been compiled without transaction support, such as a OS function or C library.
>>
>>
>> We've actually discussed this quite a bit. The current intent is to allow that, but not require it, in the next TS, with the hope of getting feedback about whether this is the correct approach. The advantage is clearly that it's nice and simple. The disadvantage is that for an STM or hybrid TM implementation it turns lots of things into subtle non-portable, hard-to-address performance problems. I think there are arguments on both sides.
>>
>> You're right that if we decide to go this way in the eventual standard, we can drop the constexpr stuff. Which would be nice.
>>
>>
>> Generally, I think we’ll see TSX style transactions benefit real world apps over the next few years, and I don’t think compiler-level software transactional memory implementations will ever be success, due to the overhead and inability to know what state really is shared and needs to pay the cost of transaction tracking. IMO the sweet spot for unbounded transactions will be C++ libraries with explicit transactional variables and new container classes designed for transaction-friendly, mostly-functional use cases. The current proposal is plenty to achieve these libraries layered on top of bounded hardware transactions in standard C++.
>>
>> Tim
>>
>>> On Jan 6, 2020, at 7:57 PM, Hans Boehm <boehm_at_[hidden]> wrote:
>>>
>>>
>>> Apologies for the very late follow-up here.
>>>
>>> Thanks for the responses!
>>>
>>> I'm not sure we communicated the use of constexpr here properly. At least my view is the problems we are trying to solve here are:
>>>
>>> 1) Assuming we don't have a mechanism for explicitly informing the compiler that certain functions are callable from transactions, nontrivial software implementations will only work if the compiler can see all the code in a transaction. In a purely HTM environment, or with the trivial global lock implementation, that isn't necessary. But we don't want to preclude software implementations. I expect that precluding software implementations would also provoke opposition from some vendors. We conjecture that the vast majority of constexpr functions will have that property, since otherwise they couldn't be executed at compile time.
>>>
>>> 2) We conjecture that most constexpr functions will “do only ordinary (non-atomic) run-time memory operations.” (I don't understand the comment about compile-time IO; it seems to me that needs wildly different semantics, since the IO device may literally not exist yet at compile time. Can you clarify?) There will be exceptions.
>>>
>>> 3) We don't want to have to specify transaction-safety for every single standard library function. But in general, whether a standard library function is defined in a header file or a separate translation unit is an implementation property. We're looking for a solution to get us out of maybe 95% of that business.
>>>
>>> 4) We realize that constexpr is somewhat of a specification hack here. It's not semantically the right property. We're hoping it's close enough to provide a reasonable default. The text will say "unless otherwise specified", and we fully expect there will be cases in which we will have to explicitly specify. We're more than open to better specification hacks. But I think we need to deal with (1).
>>>
>>> I agree that in many cases, it would be really nice if we could detect conflicts at a higher level. That seems like it's still a research issue? It also seems to require at least the kind of hairy nested transaction support that we've been trying to get away from? x++ commutes with x++, but to take advantage of that inside concurrent transactions, they individually need to run transactionally as well, right?
An implementation must specify which kinds of operations are supported (permitted to occur) in the dynamic extent of a transaction. At a minimum, this must include: ordinary (non-atomic, non-volatile) reads and writes, ordinary (non-exceptional) control flow, and calls to constexpr functions (even if their arguments are not constants), and to functions defined in the current compilation unit whose bodies would themselves be permissible inside an atomic block. ... [T]he behavior of a program that executes an unsupported operation within the dynamic scope of a transaction is undefined.
I’m questioning the need to restrict the guarantee down to local and constexpr functions. Note that even the current language permits non-transaction-safe operations in the static scope of the transaction. This is a deliberate choice, designed to accommodate code that never executes those operations:
if (foo) {
atomic {
...
if (!foo) {
cout << “oops”;
}
}
}
Given the possibility that the elided code might change foo, the compiler can’t know that this is safe.
Calls to benign external functions (functions that would be transaction safe if included in the current compilation unit) are clearly no obstacle to correct execution by HTM. They are problematic for fast execution by STM, because the difficulty of obtaining a properly instrumented copy of the function’s code may prompt the compiler to fall back to a global lock. But the lock is indeed a correct implementation, and the STM implementation must be prepared to fall back to it in any case, given the possibility of recurring dynamic conflicts.
In short, the only reason I can think of why an implementation might want to preclude calls to external functions is to avoid the need to check for the existence such calls. That’s a very small burden, and it needs to be balanced against (1) the convenience to programmers of knowing that external calls will be supported by all conforming implementations and (2) the reduction in complexity of the technical spec if we simply say that
An implementation must specify which kinds of operations are supported (permitted to occur) in the dynamic extent of a transaction. At a minimum, this must include: ordinary (non-atomic, non-volatile) reads and writes, and ordinary (non-exceptional) control flow and function calls. ... [T]he behavior of a program that executes an unsupported operation within the dynamic scope of a transaction is undefined.
- Michael
>> On Jan 9, 2020, at 1:01 PM, Hans Boehm via SG5 <sg5_at_[hidden]> wrote:
>>
>> On Mon, Jan 6, 2020 at 7:01 PM Tim Sweeney <tim.sweeney_at_[hidden]> wrote:
>>
>> My two cents: Drop the constexpr or other wording about what functions are to be transaction safe, and say an implementation must ensure that any concurrent execution of an atomic block with defined behavior must be observably equivalent to taking a global lock.
>>
>> Today that works for:
>> - a software global lock
>> - HTM implementations that fall back to a global lock
>>
>> In the future, this would also work for more thorough compiler-driven software implementations of transactional memory that abort and take a global lock if any non-transaction-safe library function is called, like I/O, or any extern function is called that may have been compiled without transaction support, such as a OS function or C library.
>>
>>
>> We've actually discussed this quite a bit. The current intent is to allow that, but not require it, in the next TS, with the hope of getting feedback about whether this is the correct approach. The advantage is clearly that it's nice and simple. The disadvantage is that for an STM or hybrid TM implementation it turns lots of things into subtle non-portable, hard-to-address performance problems. I think there are arguments on both sides.
>>
>> You're right that if we decide to go this way in the eventual standard, we can drop the constexpr stuff. Which would be nice.
>>
>>
>> Generally, I think we’ll see TSX style transactions benefit real world apps over the next few years, and I don’t think compiler-level software transactional memory implementations will ever be success, due to the overhead and inability to know what state really is shared and needs to pay the cost of transaction tracking. IMO the sweet spot for unbounded transactions will be C++ libraries with explicit transactional variables and new container classes designed for transaction-friendly, mostly-functional use cases. The current proposal is plenty to achieve these libraries layered on top of bounded hardware transactions in standard C++.
>>
>> Tim
>>
>>> On Jan 6, 2020, at 7:57 PM, Hans Boehm <boehm_at_[hidden]> wrote:
>>>
>>>
>>> Apologies for the very late follow-up here.
>>>
>>> Thanks for the responses!
>>>
>>> I'm not sure we communicated the use of constexpr here properly. At least my view is the problems we are trying to solve here are:
>>>
>>> 1) Assuming we don't have a mechanism for explicitly informing the compiler that certain functions are callable from transactions, nontrivial software implementations will only work if the compiler can see all the code in a transaction. In a purely HTM environment, or with the trivial global lock implementation, that isn't necessary. But we don't want to preclude software implementations. I expect that precluding software implementations would also provoke opposition from some vendors. We conjecture that the vast majority of constexpr functions will have that property, since otherwise they couldn't be executed at compile time.
>>>
>>> 2) We conjecture that most constexpr functions will “do only ordinary (non-atomic) run-time memory operations.” (I don't understand the comment about compile-time IO; it seems to me that needs wildly different semantics, since the IO device may literally not exist yet at compile time. Can you clarify?) There will be exceptions.
>>>
>>> 3) We don't want to have to specify transaction-safety for every single standard library function. But in general, whether a standard library function is defined in a header file or a separate translation unit is an implementation property. We're looking for a solution to get us out of maybe 95% of that business.
>>>
>>> 4) We realize that constexpr is somewhat of a specification hack here. It's not semantically the right property. We're hoping it's close enough to provide a reasonable default. The text will say "unless otherwise specified", and we fully expect there will be cases in which we will have to explicitly specify. We're more than open to better specification hacks. But I think we need to deal with (1).
>>>
>>> I agree that in many cases, it would be really nice if we could detect conflicts at a higher level. That seems like it's still a research issue? It also seems to require at least the kind of hairy nested transaction support that we've been trying to get away from? x++ commutes with x++, but to take advantage of that inside concurrent transactions, they individually need to run transactionally as well, right?
Received on 2020-01-09 15:05:16