This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Improve binary size when using __transaction
ClosedPublic

Authored by philnik on Sep 11 2022, 2:08 AM.

Details

Summary

__exception_guard is a no-op in -fno-exceptions mode to produce better code-gen. This means that we don't provide the strong exception guarantees. However, Clang doesn't generate cleanup code with exceptions disabled, so even if we wanted to provide the strong exception guarantees we couldn't. This is also only relevant for constructs with a stack of -fexceptions > -fno-exceptions > -fexceptions code, since the exception can't be caught where exceptions are disabled. While -fexceptions > -fno-exceptions is quite common (e.g. libc++.dylib > -fno-exceptions), having another layer with exceptions enabled seems a lot less common, especially one that tries to catch an exception through -fno-exceptions code.

Fixes https://github.com/llvm/llvm-project/issues/56783

Diff Detail

Event Timeline

philnik created this revision.Sep 11 2022, 2:08 AM
Herald added a project: Restricted Project. · View Herald TranscriptSep 11 2022, 2:08 AM
philnik requested review of this revision.Sep 11 2022, 2:08 AM
Herald added a project: Restricted Project. · View Herald TranscriptSep 11 2022, 2:08 AM
Herald added a reviewer: Restricted Project. · View Herald Transcript

@hans @alexfh @joanahalili Could you check whether this patch fixes the problems encountered in D128146 (i.e. applying this patch shouldn't increase the binary size much)? Compiling

#include <__utility/transaction.h>

void do_something();
void do_something_else();

void func() {
  std::__transaction __guard([] { do_something(); });
  do_something_else();
  __guard.__complete();
}

with clang++ -std=c++2b -fno-exceptions -O3 ./transaction.cpp -c -g shows a decrease of about 400bytes in binary size with this patch.

huixie90 added inline comments.Sep 11 2022, 12:06 PM
libcxx/include/__utility/transaction.h
90–104

having this __transaction class as a noop class in _LIBCPP_NO_EXCEPTIONS mode might cause confusions, because the class name does not indicate anything to do with exceptions (and could be misused as a general purpose scope_guard).

what about having a function __make_exception_guard, which returns the __transaction class in the exception mode, and returns a __noop_transaction class in the noexception mode?

Mordante requested changes to this revision.Sep 12 2022, 10:54 AM

I'm not fond of this approach, due to the different behaviour whether or not exceptions are enabled.

libcxx/include/__memory/uninitialized_algorithms.h
550–551

Can you please stop using auto everywhere? This has been requested several time by several reviewers. It really makes it unneeded hard to review code. The LLVM developer policy explains why this style isn't wanted
https://llvm.org/docs/CodingStandards.html#use-auto-type-deduction-to-make-code-more-readable.

(In library code there might be reasons to use auto a bit more often, but this isn't one of these places.)

libcxx/include/__utility/transaction.h
90–104

+1 for the confusion. What makes it more confusing is the usage of _NOEXCEPT in this class. Isn't that useless when exceptions are disabled? Please also some comment why this class it to be used as a no-op.

I'm not even sure whether we really want this class. I expect a transaction that is not committed when it goes out of scope to do a rollback.

void __foo() {
  __transaction __t;
  ... // do stuff
  if(!__happy())
    return;
  __t.__complete();
}

In that way this class behaves different depending on whether or not exceptions are enabled.

Based on the failure in the 'No exception' CI the behaviour above was intended to be working.

This revision now requires changes to proceed.Sep 12 2022, 10:54 AM
hans added a comment.Sep 14 2022, 5:16 PM

@hans Could you check whether this patch fixes the problems encountered in D128146 (i.e. applying this patch shouldn't increase the binary size much)?

Apologies for the delayed reply.

Since D128146 we've switched to compressed debug info sections for that binary, so the numbers are slightly different, but with the current patch I measure an increase of 2.45 MB (0.16%).

ldionne accepted this revision.Sep 15 2022, 9:45 AM

Since D128146 we've switched to compressed debug info sections for that binary, so the numbers are slightly different, but with the current patch I measure an increase of 2.45 MB (0.16%).

Thanks for the numbers! I think this might be the best we can do from the library side, apart from adding some additional _LIBCPP_NODEBUG attributes (but that also kind of breaks the debugging experience). This must be caused by additional instantiations of the class template.

libcxx/include/__memory/uninitialized_algorithms.h
550–551

I fully agree that we shouldn't blindly use auto, I think it generally obscures the code. IMO these "almost always use auto" advice should never have been given, they were just too easy to mis-interpret and exaggerate on.

That being said, the alternative here is:

std::__transaction<_AllocatorDestroyRangeReverse<_Alloc, _Iter2> > __guard(_AllocatorDestroyRangeReverse<_Alloc, _Iter2>(__alloc, __destruct_first, __first2));

IMO, since we have a std::__make_transaction function, we don't gain much from repeating the type. This is just my .02, I don't really care about this specific instance. I think this one is reasonable, but I agree with @Mordante in the general case about not abusing auto (I personally don't remember seeing @philnik abuse it, though).

libcxx/include/__utility/transaction.h
90–104

I fully agree with the other reviewers, I think it would be wrong to make __transaction a no-op based on whether exceptions are enabled. However, I think it's only an issue because of the semantics associated to the name __transaction, which does not imply something related to exceptions.

I'd suggest:

template <class _Rollback>
struct __noop_transaction {
  __noop_transaction() = delete;
  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_NODEBUG __noop_transaction(_Rollback) {}
  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_NODEBUG __noop_transaction(__transaction&&)
      _NOEXCEPT_(is_nothrow_move_constructible<_Rollback>::value) {}
  __noop_transaction(const __transaction&) = delete;
  __noop_transaction& operator=(const __transaction&) = delete;
  __noop_transaction& operator=(__transaction&&) = delete;

  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_NODEBUG void __complete() _NOEXCEPT {}
  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_NODEBUG ~ __noop_transaction() {}
};


#ifndef _LIBCPP_NO_EXCEPTIONS
template <class _Rollback>
using __exception_guard = __transaction<_Rollback>;
#else
template <class _Rollback>
using __exception_guard = __noop_transaction<_Rollback>;
#endif

template <class _Rollback>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR __exception_guard<_Rollback> __make_exception_guard(_Rollback __rollback) {
  return __exception_guard <_Rollback>(std::move(__rollback));
}

Also, you should use __exception_guard instead of __transaction in most existing places where __transaction is used right now.

105

Let's also add some really simple tests for this new __exception_guard class.

111

Not attached: can you please link to https://github.com/llvm/llvm-project/issues/56783 somewhere?

philnik edited the summary of this revision. (Show Details)Sep 20 2022, 1:49 AM
philnik updated this revision to Diff 461509.Sep 20 2022, 1:55 AM
philnik marked 2 inline comments as done.
  • Address comments

__transaction is currently used exclusively for handling exception guarantees, so I decided to rename the class instead of introducing multiple classes. We can still re-add a __transaction class later if we have use cases for it.

philnik marked 5 inline comments as done.Sep 20 2022, 1:58 AM
philnik added inline comments.
libcxx/include/__utility/transaction.h
90–104

_NOEXCEPT makes a difference, even when exceptions are disabled. noexcept(expr) still isn't always true.

ldionne accepted this revision.Sep 23 2022, 8:36 AM
ldionne added inline comments.
libcxx/include/__utility/exception_guard.h
97–98

Just for consistency with the other branch of the #if.

huixie90 accepted this revision.Sep 23 2022, 9:13 AM
Mordante added inline comments.
libcxx/include/__memory/uninitialized_algorithms.h
550–551

I've seen several places where auto was used which resulted in me to spending extra time to figure out the exact type used. I've seen @EricWF asking about less auto in the past too. (I don't have concrete examples, and I don't really want to spend time looking at when I asked it.)

Since __make_transaction is not a standard function I need to check whether it really creates a __transation. Using __transaction instead of auto would have saved me that verification.

Another issue I have with auto that it's not always clear what the intended type is. Especially when relying on C++ rules; where I'm not sure whether the writer had the same view of these rules as I have.

philnik updated this revision to Diff 463908.Sep 29 2022, 8:07 AM
  • Generate files
philnik updated this revision to Diff 470022.Oct 23 2022, 4:28 PM
  • Rebased
  • Generate files
philnik updated this revision to Diff 470293.Oct 24 2022, 2:39 PM
  • Try to fix CI

I'm not fond of the current state; it still has most of the semantics of the transaction instead of being a exception guard. I think with my suggestion we turn the class in a real exception guard and we never have the risk of "forgetting" to complete.

libcxx/include/__memory/uninitialized_algorithms.h
550–552

This really makes it a lot easier to review and maintain.

libcxx/include/__utility/exception_guard.h
78

When this class is intended to be an exception guard it doesn't need this function nor the __completed_ member. Instead we can do something along the lines of:

#ifndef _LIBCPP_NO_EXCEPTIONS
#if _LIBCPP_STD_VER > 14
  _LIBCPP_HIDE_FROM_ABI bool __need_rollback() { return uncaught_exceptions(); } 
#else
  _LIBCPP_HIDE_FROM_ABI bool __need_rollback() { return uncaught_exception(); } 
#endif


 _LIBCPP_CONSTEXPR_SINCE_CXX20 ~__exception_guard() {
    if(__need_rollback())
      __rollback_();
  }
#endif

With some extra boilerplate to add _LIBCPP_CONSTEXPR_SINCE_CXX20.

Then we can even use most of this class when _LIBCPP_NO_EXCEPTIONS is defined. Then it just does an extra store of the never used __rollback_ member.

philnik marked 2 inline comments as done.Oct 26 2022, 5:30 PM
philnik added inline comments.
libcxx/include/__memory/uninitialized_algorithms.h
550–552

This still doesn't work, since it uses CTAD.

libcxx/include/__utility/exception_guard.h
78

This would break when running in a destructor though, right? Then uncaught_exception() might return true even if we don't actually want to roll back a transaction. That's the whole reason uncaught_exceptions() was added.

ldionne accepted this revision.Oct 31 2022, 10:25 AM
ldionne added inline comments.
libcxx/include/__memory/uninitialized_algorithms.h
550–552

Shouldn't this work since we have _LIBCPP_CTAD_SUPPORTED_FOR_TYPE?

libcxx/include/__utility/exception_guard.h
78

IMO we should strive to keep the scope that the __exception_guard is guarding explicit. If you use uncaught_exceptions (or other) to detect whether an exception was thrown, then you'd have to use the class as

{ // begin new scope
  __exception_guard guard = ...;
  // actions...
} // end scope explicitly here

This may not always be convenient. Instead, being able to explicitly say "okay, here the thing I was guarding for exceptions is done" makes the code clearer.

libcxx/test/libcxx/utilities/exception_guard.pass.cpp
26

Might want to rename t to g in the tests here.

philnik updated this revision to Diff 472571.Nov 2 2022, 4:18 AM
philnik marked 4 inline comments as done.

Address comments

libcxx/include/__memory/uninitialized_algorithms.h
550–552

The problem is that this has to work pre-C++17, not that there aren't deduction guides.

ldionne added inline comments.Nov 15 2022, 8:55 AM
libcxx/include/__utility/exception_guard.h
78

I just had a discussion with Mark where he explained why he wasn't a big fan of the current behavior, where exception_guard will behave differently whether exceptions are enabled or not. I think we should change the no-exceptions version to

template <class _Rollback>
struct __exception_guard {
  __exception_guard() = delete;
  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_NODEBUG __exception_guard(_Rollback) {}
  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_NODEBUG __exception_guard(__exception_guard&&)
      _NOEXCEPT_(is_nothrow_move_constructible<_Rollback>::value) {}
  __exception_guard(__exception_guard const&) = delete;
  __exception_guard& operator=(__exception_guard const&) = delete;
  __exception_guard& operator=(__exception_guard&&) = delete;

  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_NODEBUG void __complete() _NOEXCEPT { __completed_ = true; }
  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_NODEBUG ~__exception_guard() { _LIBCPP_ASSERT(__completed_); }

private:
  bool __completed_;
};

This way, we enforce the intended usage of the class -- when exceptions are disabled, __complete() should always get called. And concretely this will get optimized away.

The reason I'm not a fan of depending on uncaught_exceptions() is that the function is defined in libc++abi.dylib. I am concerned about adding a call to a function hidden behind an ABI boundary that the optimizer doesn't know anything about, when instead we could just be setting and checking a bool visible to the optimizer in this very scope.

philnik updated this revision to Diff 477271.Nov 22 2022, 11:45 AM
philnik marked 2 inline comments as done.
  • Rebased
  • Address comments
ldionne requested changes to this revision.Nov 24 2022, 6:54 AM
ldionne added inline comments.
libcxx/include/__utility/exception_guard.h
106
libcxx/test/libcxx/utilities/exception_guard.pass.cpp
28

This doesn't make sense to me. I think this whole test case doesn't make sense when exceptions are disabled. For example, this would fail when exceptions are disabled and assertions are enabled.

54

Same here, this test case doesn't make sense when exceptions are disabled.

This revision now requires changes to proceed.Nov 24 2022, 6:54 AM
philnik updated this revision to Diff 478573.Nov 29 2022, 6:51 AM
philnik marked 3 inline comments as done.

Address comments

philnik updated this revision to Diff 481185.Dec 8 2022, 12:57 AM

Try to fix CI

philnik updated this revision to Diff 481191.Dec 8 2022, 1:10 AM

Fix includes

philnik updated this revision to Diff 481196.Dec 8 2022, 1:43 AM

Fix modules build

ldionne accepted this revision.Dec 8 2022, 8:39 AM

An interesting thought occurred to me last night. Let's say we have the following sandwich of code:

A // -fexceptions
B // -fno-exceptions
C // -fexceptions

Now assume that C throws an exception. The current behavior is that the exception will propagate through B (where it can't be caught) and then potentially be caught inside A. I don't know how common the full sandwich is, however a very common example of B and C are B = any-code-compiled-without-exceptions and C = libc++.dylib on most platforms. I would presume that this situation does arise in the wild.

We looked at the code generation in -fno-exceptions and the B slice of the sandwich simply does not contain any cleanup code (and hence no call to __transaction's destructor). If it did, the fact that we have a _LIBCPP_ASSERT inside the destructor would be a behavioral change where instead of letting the exception propagate freely through B (and potentially being caught higher up), we would now instead turn it into a hard error via __libcpp_verbose_abort.

We also discussed the possibility of not making this change at all so that the destructor can be called to clean things up if needed. However, since Clang already does not generate any cleanup code when -fno-exceptions is used, it looks like the choice not to provide the usual exception safety guarantees in -fno-exceptions code has already been made, and we're just following suite.

@philnik Can you please summarize this in a comment somewhere in __transaction? I think this is useful for posterity since it's not exactly obvious.

philnik updated this revision to Diff 481847.Dec 10 2022, 5:18 AM
  • Address comments
  • Try to fix CI
Mordante requested changes to this revision.Dec 12 2022, 9:31 AM

In general quite happy with the current form! But I think I spotted a bug, and found some minor nits.

Can you also update the title of the patch.

libcxx/include/__utility/exception_guard.h
30

replace transaction with exception guard/__exception_guard in this paragraph.

41

Nit: I prefer comments to stay in the 80 column limit. These long lines are less easy to read. For a single line it's not too bad, but this is quite a bit of text.

41

Maybe copy this paragraph in the commit message.

59

Nice comment!

68

Since this is a new file can you s/_VSTD/std/ in this file?

106

I think there is a bug here. The normal move sets __other.__completed_ this one doesn't.
I think there should be a test for this case too.

This revision now requires changes to proceed.Dec 12 2022, 9:31 AM
philnik edited the summary of this revision. (Show Details)Dec 26 2022, 1:48 AM
philnik updated this revision to Diff 485275.Dec 26 2022, 2:03 AM
philnik marked 6 inline comments as done.

Address comments

philnik updated this revision to Diff 485296.Dec 26 2022, 5:30 AM

Try to fix CI

philnik updated this revision to Diff 485338.Dec 26 2022, 7:40 PM

Try to fix CI

Mordante accepted this revision.Jan 22 2023, 4:54 AM

LGTM!

This revision is now accepted and ready to land.Jan 22 2023, 4:54 AM
This revision was landed with ongoing or failed builds.Jan 22 2023, 7:57 PM
This revision was automatically updated to reflect the committed changes.
bgraur added a subscriber: bgraur.Jan 31 2023, 4:14 AM

We're seeing a lot of fallout from this patch and they all look related to the ODR violations that seem to be intentionally added here: both the patch description and the comment for the __exception_guard mention explicitly that combinations of code compiled with exceptions and without exceptions are common.

Am I missing something here?

We're seeing a lot of fallout from this patch and they all look related to the ODR violations that seem to be intentionally added here: both the patch description and the comment for the __exception_guard mention explicitly that combinations of code compiled with exceptions and without exceptions are common.

Am I missing something here?

Do you have a specific example? We have similar ODR violations all over the code base related to exception handling.

We're seeing a lot of fallout from this patch and they all look related to the ODR violations that seem to be intentionally added here: both the patch description and the comment for the __exception_guard mention explicitly that combinations of code compiled with exceptions and without exceptions are common.

Am I missing something here?

Do you have a specific example?

Well, we have tons of different examples, but most of them are difficult to isolate and reduce. Some of them manifest in linker complaining about invalid debug information in thinlto mode. Some manifest as runtime crashes and/or sanitizer errors. We'll try to provide something specific, but it may take days to come up with something.

We have similar ODR violations all over the code base related to exception handling.

Well, I don't know how bad these were, but we definitely didn't see any related issues previously. Maybe this commit was the straw that broke the camel's back or it introduced a novel kind of an ODR violation when using mixed exception modes, e.g. that previously the differences were limited to exception handling within function bodies and now the definition of the __exception_guard class being different is somehow more problematic.

the definition of the __exception_guard class being different is somehow more problematic.

That's correct - Clang/LLVM debug info under LTO does require structures with linkage to have consistent size at least.
The sort of errors are:

fragment is larger than or outside of variable
  call void @llvm.dbg.declare(metadata ptr undef, metadata !4274, metadata !DIExpression(DW_OP_LLVM_fragment, 200, 56)), !dbg !4288
!4274 = !DILocalVariable(name: "__guard", scope: !4275, file: !2826, line: 550, type: !2906)

Because LLVM LTO is validating that the debug info describing a variable makes sense given the type - but the type is deduplicated based on the linkage name of the type - so some debug info was emitted in some +exceptions code that is larger than the type definition taken from some -exceptions code.

I think some possible fixes would include

  • make the structure the same size regardless of +/-exceptions
  • use a macro to choose between two differently-linkage-named types (yeah, this is still an ODR violaiton for the function definitions that use these different entities - but within the realms of things we do for +/-exception compatibility, and nothing validates that ODR function definitions are identical)

I'll see if I can repro this with godbolt, but I think this ^ should be enough to justify a timely revert and/or fix. This patch is broken for mixed exceptions LTO builds.

the definition of the __exception_guard class being different is somehow more problematic.

That's correct - Clang/LLVM debug info under LTO does require structures with linkage to have consistent size at least.
The sort of errors are:

fragment is larger than or outside of variable
  call void @llvm.dbg.declare(metadata ptr undef, metadata !4274, metadata !DIExpression(DW_OP_LLVM_fragment, 200, 56)), !dbg !4288
!4274 = !DILocalVariable(name: "__guard", scope: !4275, file: !2826, line: 550, type: !2906)

Because LLVM LTO is validating that the debug info describing a variable makes sense given the type - but the type is deduplicated based on the linkage name of the type - so some debug info was emitted in some +exceptions code that is larger than the type definition taken from some -exceptions code.

I think some possible fixes would include

  • make the structure the same size regardless of +/-exceptions
  • use a macro to choose between two differently-linkage-named types (yeah, this is still an ODR violaiton for the function definitions that use these different entities - but within the realms of things we do for +/-exception compatibility, and nothing validates that ODR function definitions are identical)

I'll see if I can repro this with godbolt, but I think this ^ should be enough to justify a timely revert and/or fix. This patch is broken for mixed exceptions LTO builds.

Please don't revert. Other patches depend on this and it should be a trivial fix. We can just add an ABI tag to the noexcept version of the class IIUC. Could you confirm that? If that is the case I can make a patch.

the definition of the __exception_guard class being different is somehow more problematic.

That's correct - Clang/LLVM debug info under LTO does require structures with linkage to have consistent size at least.
The sort of errors are:

fragment is larger than or outside of variable
  call void @llvm.dbg.declare(metadata ptr undef, metadata !4274, metadata !DIExpression(DW_OP_LLVM_fragment, 200, 56)), !dbg !4288
!4274 = !DILocalVariable(name: "__guard", scope: !4275, file: !2826, line: 550, type: !2906)

Because LLVM LTO is validating that the debug info describing a variable makes sense given the type - but the type is deduplicated based on the linkage name of the type - so some debug info was emitted in some +exceptions code that is larger than the type definition taken from some -exceptions code.

I think some possible fixes would include

  • make the structure the same size regardless of +/-exceptions
  • use a macro to choose between two differently-linkage-named types (yeah, this is still an ODR violaiton for the function definitions that use these different entities - but within the realms of things we do for +/-exception compatibility, and nothing validates that ODR function definitions are identical)

I'll see if I can repro this with godbolt, but I think this ^ should be enough to justify a timely revert and/or fix. This patch is broken for mixed exceptions LTO builds.

Please don't revert. Other patches depend on this and it should be a trivial fix. We can just add an ABI tag to the noexcept version of the class IIUC. Could you confirm that? If that is the case I can make a patch.

Not quite sure what you mean - got a rough example of what an ABI tag would look like in this case? (looking through the libc++ sources notihng immediately stands out) - an extra template parameter (I see "Abi" template parameters in some places, for instance) or some other technique?

the definition of the __exception_guard class being different is somehow more problematic.

That's correct - Clang/LLVM debug info under LTO does require structures with linkage to have consistent size at least.
The sort of errors are:

fragment is larger than or outside of variable
  call void @llvm.dbg.declare(metadata ptr undef, metadata !4274, metadata !DIExpression(DW_OP_LLVM_fragment, 200, 56)), !dbg !4288
!4274 = !DILocalVariable(name: "__guard", scope: !4275, file: !2826, line: 550, type: !2906)

Because LLVM LTO is validating that the debug info describing a variable makes sense given the type - but the type is deduplicated based on the linkage name of the type - so some debug info was emitted in some +exceptions code that is larger than the type definition taken from some -exceptions code.

I think some possible fixes would include

  • make the structure the same size regardless of +/-exceptions
  • use a macro to choose between two differently-linkage-named types (yeah, this is still an ODR violaiton for the function definitions that use these different entities - but within the realms of things we do for +/-exception compatibility, and nothing validates that ODR function definitions are identical)

I'll see if I can repro this with godbolt, but I think this ^ should be enough to justify a timely revert and/or fix. This patch is broken for mixed exceptions LTO builds.

Please don't revert. Other patches depend on this and it should be a trivial fix. We can just add an ABI tag to the noexcept version of the class IIUC. Could you confirm that? If that is the case I can make a patch.

Not quite sure what you mean - got a rough example of what an ABI tag would look like in this case? (looking through the libc++ sources notihng immediately stands out) - an extra template parameter (I see "Abi" template parameters in some places, for instance) or some other technique?

I mean [[gnu::abi_tag("-fno-exceptions")]].

The ABI tag approach fixes the ODR issue I was looking at

@@ -60,7 +60,7 @@

 #ifndef _LIBCPP_NO_EXCEPTIONS
 template <class _Rollback>
-struct __exception_guard {
+struct __attribute__((__abi_tag__(("fexceptions")))) __exception_guard {
   __exception_guard() = delete;

   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 explicit __exception_guard(_Rollback __rollback)
@@ -89,7 +89,7 @@
 };
 #else  // _LIBCPP_NO_EXCEPTIONS
 template <class _Rollback>
-struct __exception_guard {
+struct __attribute__((__abi_tag__(("fnoexceptions")))) __exception_guard  {
   __exception_guard() = delete;
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_NODEBUG explicit __exception_guard(_Rollback) {}

The ABI tag approach fixes the ODR issue I was looking at

@@ -60,7 +60,7 @@

 #ifndef _LIBCPP_NO_EXCEPTIONS
 template <class _Rollback>
-struct __exception_guard {
+struct __attribute__((__abi_tag__(("fexceptions")))) __exception_guard {
   __exception_guard() = delete;

   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 explicit __exception_guard(_Rollback __rollback)
@@ -89,7 +89,7 @@
 };
 #else  // _LIBCPP_NO_EXCEPTIONS
 template <class _Rollback>
-struct __exception_guard {
+struct __attribute__((__abi_tag__(("fnoexceptions")))) __exception_guard  {
   __exception_guard() = delete;
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_NODEBUG explicit __exception_guard(_Rollback) {}

Same patch fixes a large fraction of other issues we've been looking at.

alexfh added a comment.Feb 1 2023, 4:27 AM

@philnik could you commit one of the proposed abi_tag fixes?

alexfh added a comment.Feb 1 2023, 6:02 AM

@philnik could you commit one of the proposed abi_tag fixes?

Actually, I went ahead and committed it as 561105fb9d3a16f7fb8c718cc5da71b11f17a144 to unblock us. Hopefully, that's small and obvious enough to not violate the code review policies.

@philnik could you commit one of the proposed abi_tag fixes?

Actually, I went ahead and committed it as 561105fb9d3a16f7fb8c718cc5da71b11f17a144 to unblock us. Hopefully, that's small and obvious enough to not violate the code review policies.

libc++ has a pretty good precommit test infra that gets triggered when a review is created, so in the future it would be better to create a review and wait for CI before landing w/o review.

Although gnu::abi_tag was suggested, I took that to mean __attribute__((__abi_tag__(...))) when I tested the suggestion locally, as elsewhere libc++ uses ABI tags like so:

#    define _LIBCPP_HIDE_FROM_ABI                                                                                      \
      _LIBCPP_HIDDEN _LIBCPP_EXCLUDE_FROM_EXPLICIT_INSTANTIATION                                                       \
          __attribute__((__abi_tag__(_LIBCPP_TOSTRING(_LIBCPP_VERSIONED_IDENTIFIER))))
philnik added a comment.EditedFeb 1 2023, 7:06 AM

@philnik could you commit one of the proposed abi_tag fixes?

Actually, I went ahead and committed it as 561105fb9d3a16f7fb8c718cc5da71b11f17a144 to unblock us. Hopefully, that's small and obvious enough to not violate the code review policies.

libc++ has a pretty good precommit test infra that gets triggered when a review is created, so in the future it would be better to create a review and wait for CI before landing w/o review.

Although gnu::abi_tag was suggested, I took that to mean __attribute__((__abi_tag__(...))) when I tested the suggestion locally, as elsewhere libc++ uses ABI tags like so:

#    define _LIBCPP_HIDE_FROM_ABI                                                                                      \
      _LIBCPP_HIDDEN _LIBCPP_EXCLUDE_FROM_EXPLICIT_INSTANTIATION                                                       \
          __attribute__((__abi_tag__(_LIBCPP_TOSTRING(_LIBCPP_VERSIONED_IDENTIFIER))))

I reverted, since that breaks C++03. I'll commit a proper fix probably by the end of the day.

I've uploaded D143071 to fix the problem. I talk to Louis later today, so a fix should be commited within the next 6 hours or so.

alexfh added a comment.Feb 1 2023, 8:19 AM

I've uploaded D143071 to fix the problem. I talk to Louis later today, so a fix should be commited within the next 6 hours or so.

Thanks and apologies for doing this myself in hurry - this issue has been blocking our release testing for quite some time and I needed at least a temporary solution in the tree. I'll try to follow the review process next time.