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.
Details
- Reviewers
ldionne alexfh - Group Reviewers
Restricted Project - Commits
- rG1a17739d3aa7: [libc++] Avoid ODR violations in __exception_guard
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
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. |
libcxx/include/__config | ||
---|---|---|
446–447 ↗ | (On Diff #493940) | This makes it possible to use the tag for other cases where we have ODR violations. |
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? |
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. |
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? |
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. |
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. |
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.)
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.
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?