Date: Thu, 9 Jan 2020 15:24:56 -0800
As was pointed out in the meeting there seem to be two reasons to restrict
calls to other translation units, probably by relying on constexpr, neither
100% convincing:
1) To accommodate STM or hybrid TM. I agree that there is no correctness
issue with supporting such calls. But in the past, we had significant
concerns about usability of such a system. You can't really tell whether
you're getting an even minimally scalable implementation of a simple
standard library call without knowing whether its implementation resides in
the same TU, which is now not easily determinable, and may change with the
next release.
2) It makes it easier for an implementation to report when a data-race-free
atomic{} block isn't really atomic because it internally uses e.g. locks,
and can thus communicate with other threads before the (now irrevocable)
transaction commits. Without the restriction, this would presumably require
dynamic tests associated with locks and atomics. With it, the checks could
be restricted to TUs containing atomic blocks, e.g. by cloning code called
from within transactions.
Another weak argument is that since you can know you are within a
transaction, you could presumably generate more HTM-friendly code in some
cases. I don't know whether that's an issue with any existing hardware. Are
there useful compiler-generated instructions that always fail in some HTM
implementation?
I agree that using constexpr is imperfect. And there are cases in which the
library vendor will need to change their code to adapt. I think we're only
proposing using constexpr for the standard library; for other libraries the
theory is that the user can tell what's defined in the same translation
unit.
On Thu, Jan 9, 2020 at 1:30 PM Jens Maurer via SG5 <sg5_at_[hidden]>
wrote:
> On 09/01/2020 22.04, Michael L. Scott via SG5 wrote:
> > 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.
>
> The other problem is library functions: If we allow all functions
> (even non-constexpr ones), we don't have an easy library spec marker
> to indicate which functions are required to be supported and
> which ones aren't. So, we'd need to specify library functions
> individually or by broad clause numbers. For example, [atomics]
> and [thread] functions are right out, but most of [utilities]
> is ok... Except where exactly is setjmp / longjmp defined?
>
> A slightly related issue:
>
> Consider a "helpful" library implementation that does
> special debug output (maybe in a file on the side) when
> compiling certain algorithms in "debug" mode. The library
> might be third-party and not privy to the existence of TM.
> However, its algorithms are constexpr, and it avoids
> that debug output if std::is_constant_evaluated() is true.
> That library has no way of avoiding the debug output =
> possibly undefined behavior if it's called inside an atomic
> block.
>
> Jens
> --
> SG5 mailing list
> SG5_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg5
>
calls to other translation units, probably by relying on constexpr, neither
100% convincing:
1) To accommodate STM or hybrid TM. I agree that there is no correctness
issue with supporting such calls. But in the past, we had significant
concerns about usability of such a system. You can't really tell whether
you're getting an even minimally scalable implementation of a simple
standard library call without knowing whether its implementation resides in
the same TU, which is now not easily determinable, and may change with the
next release.
2) It makes it easier for an implementation to report when a data-race-free
atomic{} block isn't really atomic because it internally uses e.g. locks,
and can thus communicate with other threads before the (now irrevocable)
transaction commits. Without the restriction, this would presumably require
dynamic tests associated with locks and atomics. With it, the checks could
be restricted to TUs containing atomic blocks, e.g. by cloning code called
from within transactions.
Another weak argument is that since you can know you are within a
transaction, you could presumably generate more HTM-friendly code in some
cases. I don't know whether that's an issue with any existing hardware. Are
there useful compiler-generated instructions that always fail in some HTM
implementation?
I agree that using constexpr is imperfect. And there are cases in which the
library vendor will need to change their code to adapt. I think we're only
proposing using constexpr for the standard library; for other libraries the
theory is that the user can tell what's defined in the same translation
unit.
On Thu, Jan 9, 2020 at 1:30 PM Jens Maurer via SG5 <sg5_at_[hidden]>
wrote:
> On 09/01/2020 22.04, Michael L. Scott via SG5 wrote:
> > 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.
>
> The other problem is library functions: If we allow all functions
> (even non-constexpr ones), we don't have an easy library spec marker
> to indicate which functions are required to be supported and
> which ones aren't. So, we'd need to specify library functions
> individually or by broad clause numbers. For example, [atomics]
> and [thread] functions are right out, but most of [utilities]
> is ok... Except where exactly is setjmp / longjmp defined?
>
> A slightly related issue:
>
> Consider a "helpful" library implementation that does
> special debug output (maybe in a file on the side) when
> compiling certain algorithms in "debug" mode. The library
> might be third-party and not privy to the existence of TM.
> However, its algorithms are constexpr, and it avoids
> that debug output if std::is_constant_evaluated() is true.
> That library has no way of avoiding the debug output =
> possibly undefined behavior if it's called inside an atomic
> block.
>
> Jens
> --
> SG5 mailing list
> SG5_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/sg5
>
Received on 2020-01-09 17:27:39