This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Avoid ODR violations in __exception_guard
ClosedPublic

Authored by philnik on Feb 1 2023, 7:22 AM.

Details

Reviewers
ldionne
alexfh
Group Reviewers
Restricted Project
Commits
rG1a17739d3aa7: [libc++] Avoid ODR violations in __exception_guard
Summary

Having an ODR violation with __exception_guard seems to be problematic in LTO builds. To avoid the ODR violation, give the class different names for exception/no-exceptions mode and have an alias to the correct class.

Diff Detail

Event Timeline

philnik created this revision.Feb 1 2023, 7:22 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 1 2023, 7:22 AM
philnik requested review of this revision.Feb 1 2023, 7:22 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 1 2023, 7:22 AM
Herald added a reviewer: Restricted Project. · View Herald Transcript
rupprecht added inline comments.
libcxx/include/__config
446–447 ↗(On Diff #493940)

Is there a reason this is conditionally defined? The value of LIBCPP_EXCEPTION_ABI_TAG is only used when _LIBCPP_NO_EXCEPTIONS is enabled, so it would be possible to unconditionally set it to this.

philnik added inline comments.Feb 1 2023, 7:54 AM
libcxx/include/__config
446–447 ↗(On Diff #493940)

This makes it possible to use the tag for other cases where we have ODR violations.

philnik updated this revision to Diff 493951.Feb 1 2023, 7:59 AM

Try to fix CI

philnik edited the summary of this revision. (Show Details)Feb 1 2023, 7:59 AM
alexfh added a subscriber: alexfh.Feb 1 2023, 8:26 AM
alexfh added inline comments.
libcxx/include/__utility/exception_guard.h
61

Should this be moved inside the definition of the class to make it clear which code is shared between the _LIBCPP_NO_EXCEPTIONS and !_LIBCPP_NO_EXCEPTIONS modes?

ldionne accepted this revision.Feb 1 2023, 8:49 AM

Thanks for looking into this. I agree with fixing this ASAP, however in the longer term we should simply encode the ABI-affecting properties of the library into the ABI tag. For example, whether the debug mode is enabled or not, whether exceptions are enabled, etc. Then that would apply library-wide. In order for that to work, we'd also need to drop always_inline and probably exclude_from_explicit_instantiation from those macros, which is a WIP.

To avoid making it look like this is the final solution, I would avoid introducing a macro in __config and I would simply use two differently-named classes.

LGTM w/ comments, also this needs to be cherry-picked onto release/16.x.

libcxx/include/__utility/exception_guard.h
62–63

Instead, let's define two classes, one is __exception_guard_exceptions and the other one is __exception_guard_noexceptions. Then we can have an alias to the right one.

libcxx/test/libcxx/utilities/exception_guard.odr.sh.cpp
10
12–13

Makes it easier to see what's going on.

This revision is now accepted and ready to land.Feb 1 2023, 8:49 AM
philnik updated this revision to Diff 493994.Feb 1 2023, 9:52 AM
philnik marked 5 inline comments as done.

Address comments

dblaikie added inline comments.Feb 1 2023, 9:59 AM
libcxx/include/__utility/exception_guard.h
62–63

If they're going to have separate names with an alias - do they need to be/is it better that they not be preprocessor conditional, and only the definition of the alias would need the preprocessor conditionals? Or is it worth keeping the extra code out of the parsing/worth the extra preprocessor conditionality to do so?

126–130

I guess the other changes in the patch to use __make_exception_guard are because CTAD can't be used through the alias? If that's true, maybe remove the CTAD support macros here - since it won't be used portably between exception/non-exception code anyway?

philnik marked 2 inline comments as done.Feb 1 2023, 10:17 AM
philnik added inline comments.
libcxx/include/__utility/exception_guard.h
62–63

I left them in the conditional to avoid accidentally depending on one of them directly. This should be caught in code review, but letting the compiler complain makes it more obvious.

126–130

I expect us to revert part of this once Clang supports CTAD with aliases, so I'd just keep them since they will be added again later.

philnik retitled this revision from [libc++] Add abi_tag on __exception_guard to avoid ODR violations to [libc++] Avoid ODR violations in __exception_guard.Feb 1 2023, 10:19 AM
philnik edited the summary of this revision. (Show Details)
alexfh accepted this revision.Feb 1 2023, 10:35 AM

The implementation with two different class names seems much cleaner. Thanks!

philnik updated this revision to Diff 494010.Feb 1 2023, 10:50 AM
philnik marked 2 inline comments as done.

Try to fix CI

philnik updated this revision to Diff 494072.Feb 1 2023, 2:05 PM

Next try

philnik updated this revision to Diff 494090.Feb 1 2023, 3:38 PM

Next try

This revision was landed with ongoing or failed builds.Feb 1 2023, 5:07 PM
This revision was automatically updated to reflect the committed changes.
ldionne added inline comments.Feb 3 2023, 2:25 PM
libcxx/include/__utility/exception_guard.h
62–63

FWIW I would have kept only the alias in #ifdef to keep the amount of conditional code as small as possible.

smeenai added a subscriber: smeenai.EditedMar 8 2023, 9:45 PM

I'm still seeing LTO errors after this patch:

fragment covers entire variable
  call void @llvm.dbg.value(metadata i64 poison, metadata !798696, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64)), !dbg !798698
!798696 = !DILocalVariable(name: "__first", arg: 2, scope: !798693, file: !16879, line: 520, type: !159997)
fragment is larger than or outside of variable
  call void @llvm.dbg.value(metadata ptr %5, metadata !798696, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64)), !dbg !798698
!798696 = !DILocalVariable(name: "__first", arg: 2, scope: !798693, file: !16879, line: 520, type: !159997)
fragment covers entire variable
  call void @llvm.dbg.value(metadata i64 poison, metadata !798697, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64)), !dbg !798698
!798697 = !DILocalVariable(name: "__last", arg: 3, scope: !798693, file: !16879, line: 520, type: !159997)
fragment is larger than or outside of variable
  call void @llvm.dbg.value(metadata ptr %7, metadata !798697, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64)), !dbg !798698
!798697 = !DILocalVariable(name: "__last", arg: 3, scope: !798693, file: !16879, line: 520, type: !159997)
fragment is larger than or outside of variable
  call void @llvm.dbg.value(metadata ptr %9, metadata !798696, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64)), !dbg !798698
!798696 = !DILocalVariable(name: "__first", arg: 2, scope: !798693, file: !16879, line: 520, type: !159997)
fragment is larger than or outside of variable
  call void @llvm.dbg.value(metadata ptr %12, metadata !798696, metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64)), !dbg !798698
!798696 = !DILocalVariable(name: "__first", arg: 2, scope: !798693, file: !16879, line: 520, type: !159997)
LLVM ERROR: Broken module found, compilation aborted!

I went to the libc++ source state right before D133661 and was able to build fine, and then I checked out that change and applied this one directly on top (versus just checking out this revision, to ensure there were no other changes) and repro'd, so it's definitely caused by these patches. There were a bunch of other errors that this patch did fix, but not these.

Unfortunately I'm not sure how to create a standalone reproducer (this is part of a giant internal build that I can't share as-is). If I use --save-temps during LTO and then use llvm-dis to convert to textual IR to try reducing it, it just ignores the invalid debug info, which defeats the whole purpose. Does anyone have suggestions for that?

I also requested a backport to 16.0.1 in https://github.com/llvm/llvm-project/issues/61276, since it's late in the LLVM 16 release cycle now.

(Also apologies for raising this a month later; I was on an extended leave and am only getting around to evaluating libc++ 16 now.)

smeenai added a comment.EditedMar 8 2023, 10:17 PM

Okay, -print-before-all did the trick, and the local variables it's complaining about are from https://github.com/llvm/llvm-project/blob/08b65c5c9e58a4f9e79a52a0fccd70e4c4ebd43b/libcxx/include/__memory/uninitialized_algorithms.h#L520. I guess we have some ODR issue with the specific types that template is being instantiated with (reverse_iterator<reverse_iterator<internal_type*>>) that's being exposed now?

EDIT: Yup, this was entirely our fault. We were LTOing a TU built with C++14 and a TU built with C++17 together, and we use the unstable ABI configuration, so iterators ended up being different sizes in the TUs. Sorry for the noise.