This is an archive of the discontinued LLVM Phabricator instance.

[libunwind] Use `_dl_find_object` if available
ClosedPublic

Authored by avogelsgesang on Jul 27 2022, 3:26 PM.

Details

Summary

[libunwind] Use _dl_find_object if available

As shown in P2544R0 [1] and the accompanying benchmark [2], the
current unwinding logic does not scale for multi-threaded programs.
This is because dl_iterate_phdr takes a global lock.

glibc 2.35 added _dl_find_object which directly returns the unwind
info for a given target address. _dl_find_object is fully lock-free
and hence allows parallel exception unwinding on multiple threads.

With this commit, libunwind now takes advantage of _dl_find_object.
Thereby, this commit improves libunwind's performance on benchmark [2]
for unwinding exception on 20 threads from 1103ms to 78ms.
(measured on Intel Xeon Silver 4114 with 20 physical cores)

[1] https://isocpp.org/files/papers/P2544R0.html
[2] https://github.com/neumannt/exceptionperformance

Detailed performance numbers from the benchmark:

Before:

Testing unwinding performance: sqrt computation with occasional errors

testing baseline using 1 2 4 8 16 20 threads
failure rate 0%: 34 35 34 35 35 36
testing exceptions using 1 2 4 8 16 20 threads
failure rate 0%: 16 32 33 34 35 36
failure rate 0.1%: 16 32 34 36 35 36
failure rate 1%: 20 40 40 43 90 113
failure rate 10%: 59 92 140 304 880 1103
[...]

Testing invocation overhead: recursive fib with occasional errors

testing exceptions using 1 2 4 8 16 20 threads
failure rate 0%: 19 32 37 38 39 36
failure rate 0.1%: 22 32 40 40 39 34
failure rate 1%: 20 28 38 39 48 40
failure rate 10%: 25 39 44 50 92 113

After:

Testing unwinding performance: sqrt computation with occasional errors

testing baseline using 1 2 4 8 16 20 threads
failure rate 0%: 19 30 35 38 39 35
testing baseline using 1 2 4 8 16 20 threads
failure rate 0%: 32 35 33 34 34 36
testing exceptions using 1 2 4 8 16 20 threads
failure rate 0%: 16 35 33 37 35 35
failure rate 0.1%: 16 32 36 33 34 37
failure rate 1%: 21 37 39 40 40 41
failure rate 10%: 72 75 76 80 80 78
[...]

Testing invocation overhead: recursive fib with occasional errors

testing baseline using 1 2 4 8 16 20 threads
failure rate 0%: 18 35 37 34 38 37
testing exceptions using 1 2 4 8 16 20 threads
failure rate 0%: 19 33 40 40 41 39
failure rate 0.1%: 21 33 39 38 39 38
failure rate 1%: 20 36 39 40 41 40
failure rate 10%: 25 45 41 42 44 43

Diff Detail

Event Timeline

avogelsgesang created this revision.Jul 27 2022, 3:26 PM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptJul 27 2022, 3:26 PM
Herald added a reviewer: Restricted Project. · View Herald Transcript
avogelsgesang requested review of this revision.Jul 27 2022, 3:26 PM
Herald added a project: Restricted Project. · View Herald TranscriptJul 27 2022, 3:26 PM
avogelsgesang added subscribers: neumannt, MaskRay, ldionne.

not sure whom I should add as a reviewer.
@ldionne, @MaskRay you were on the last couple of libunwind reviews, are you the right reviewers for this patch? If not, do you know who could review this?

@neumannt FYI, as an alternative to D120243

Apply clang-format and fix commit title

avogelsgesang retitled this revision from [libunwind] Use `_dl_find_eh_frame` if available to [libunwind] Use `_dl_find_object` if available.Jul 28 2022, 1:24 AM
compnerd requested changes to this revision.Jul 28 2022, 8:11 AM
compnerd added a subscriber: compnerd.
compnerd added inline comments.
libunwind/src/AddressSpace.hpp
630

This static assertion doesn't make sense to me. You expect to define DLFO_STRUCT_HAS_EH_DBASE as 0 when it is available?

634

Could you move the comment to the assignment below please.

635

Hmm, I don't understand this assertion either.

642

I wonder if it makes sense to use info.dwarf_index_section_length here.

This revision now requires changes to proceed.Jul 28 2022, 8:11 AM
avogelsgesang marked an inline comment as done.

Pass info.dwarf_index_section_length instead of SIZE_MAX as requested by @compnerd

libunwind/src/AddressSpace.hpp
630

based on https://www.gnu.org/software/libc/manual/html_node/Dynamic-Linker-Introspection.html

On most targets, this macro is defined as 0. If it is defined to 1, struct dl_find_object contains an additional member dlfo_eh_dbase of type void *. It is the base address for DW_EH_PE_datarel DWARF encodings to this location.

The code here does not handle dlfo_eh_dbase, hence I want the build fail if I am on any platform where dlfo_eh_dbase exists. Hence, the static_assert checks that for DLFO_STRUCT_HAS_EH_DBASE == 0

634

The

_dl_find_object` gives us the start but not the size of the PT_GNU_EH_FRAME section.

part of this comment is actually checked by the static_assert directly below this comment.

With the explanation of the static_assert below, do you still want me to move this comment? Should I split the comment into two? Should I keep it as is?

635

from https://www.gnu.org/software/libc/manual/html_node/Dynamic-Linker-Introspection.html

On targets using DWARF-based exception unwinding, this macro expands to PT_GNU_EH_FRAME. This indicates that dlfo_eh_frame in struct dl_find_object points to the PT_GNU_EH_FRAME segment of the object. On targets that use other unwinding formats, the macro expands to the program header type for the unwinding data.

The code below assumes that dlfo_eh_frame points to the PT_GNU_EH_FRAME section. The static_assert makes this assumption explicit

compnerd requested changes to this revision.Jul 28 2022, 2:55 PM
compnerd added inline comments.
libunwind/src/AddressSpace.hpp
630

I think that I would rather have this be written as DLFO_STRUCT_HAS_EH_DBASE-0 on L608 rather than #if defined(DLFO_STRUCT_HAS_EH_DBASE) and then the static assert. Additionally, this seems like it would then cause a build failure if that was ever defined as 0.

634

A split sounds good to me.

635

The confusion on my part stems from DLFO_EH_SEGMENT_TYPE ... that seems to be a macro that would be statically defined. This is confusing as a check because it is not the type of segment that is _retrieved_, it is an assumption at build time.

This revision now requires changes to proceed.Jul 28 2022, 2:55 PM
avogelsgesang edited the summary of this revision. (Show Details)Aug 1 2022, 3:57 AM

Rephrase comments/static_assert's

avogelsgesang marked 2 inline comments as done.Aug 1 2022, 4:22 AM
avogelsgesang added inline comments.
libunwind/src/AddressSpace.hpp
630

this seems like it would then cause a build failure if that was ever defined as 0

That was my intent. My line of thinking is:
I don't know of any system which defines DLFO_STRUCT_HAS_EH_DBASE != 0. As such, I didn't handle it in this code, under the assumption that those systems are not relevant for libunwind. In case there is indeed a relevant system with DLFO_STRUCT_HAS_EH_DBASE != 0, I would rather want to get a build error than silently disabling this optimization.

But this is not my decision to make, but rather up to the maintainers/you

I would rather have this be written as DLFO_STRUCT_HAS_EH_DBASE-0 on L608 rather than #if defined(DLFO_STRUCT_HAS_EH_DBASE)

Should I also move the similar DLFO_EH_SEGMENT_TYPE == PT_GNU_EH_FRAME to L608?

634

I split the comment in the newly updated version

635

I rephrased the assert to

_dl_find_object retrieves an unexpected section type

I hope together with the updated comments, this makes the intent of the assert more easily understandable

compnerd added inline comments.Aug 1 2022, 12:44 PM
libunwind/src/AddressSpace.hpp
630

I see. I think that having the assert that its not supported is fine, but I would rather see that be more explicit before the region as:

#if DLFO_STRUCT_HAS_EH_DBASE
#error eh_dbase is not supported for ...
#endif

This makes it more obvious that it is something that we may need to address in the future and then subsequently "silently disabling" is safe - the compilation will fail.

Doing something similar for the DLFO_EH_SEGMENT_TYPE makes sense as well.

avogelsgesang marked an inline comment as done.

Turn build assertions from static_assert into #error

avogelsgesang marked 3 inline comments as done.Aug 1 2022, 2:42 PM
avogelsgesang added inline comments.
libunwind/src/AddressSpace.hpp
630

Makes sense. I updated the patch to use #if + #error

compnerd accepted this revision.Aug 2 2022, 9:51 AM

Thanks, this looks reasonable to me.

This revision is now accepted and ready to land.Aug 2 2022, 9:51 AM
avogelsgesang marked an inline comment as done.Aug 2 2022, 9:54 AM

Thanks for your review & your approval, @compnerd!

Do you know the next steps to get this landed? Is your approval sufficient or should we wait for additional reviewers?

Fix typo in error message

It should be okay to commit.

MaskRay added inline comments.Aug 2 2022, 3:58 PM
libunwind/src/AddressSpace.hpp
614

typo: the the

653

.eh_frame_hdr records the start of .eh_frame

This sentence is wrong. .eh_frame_hdr is a different section.

avogelsgesang marked 3 inline comments as done.

Fix typo in comment: the the -> the

avogelsgesang added inline comments.Aug 3 2022, 1:05 AM
libunwind/src/AddressSpace.hpp
653

PT_GNU_EH_FRAME is a bit of a misnormer as it refers to eh_frame_hdr, not eh_frame. From https://refspecs.linuxbase.org/LSB_3.0.0/LSB-PDA/LSB-PDA/progheader.html

PT_GNU_EH_FRAME:
The array element specifies the location and size of the exception handling information as defined by the .eh_frame_hdr section.

The eh_frame_hdr is hence the section retrieved by _dl_find_object. EHHeaderParser<LocalAddressSpace>::decodeEHHdr a couple of lines above decodes the eh_frame_hdr. Parsing the`eh_frame_hdr` gives us the pointer to the start of eh_frame, but doesn't give us its size. Hence, we set dwarf_section_length to SIZE_MAX and thereby rely on the zero terminator to find the end of the section.

Please let me know how to update this comment to make it more understandable.

Also, I shamelessly copied this comment from the checkForUnwindInfoSegment function. In case you want me to update also that comment, please let me know

MaskRay added inline comments.Aug 3 2022, 9:12 PM
libunwind/src/AddressSpace.hpp
640

No need to replicate the member variable name. Just say "Record the start of PT_GNU_EH_FRAME"

PT_GNU_EH_FRAME is not a section.

653

I know PT_GNU_EH_FRAME. The comment just doesn't make sense to me. I suggest something aligning:

Record the start of a FDE and use SIZE_MAX to indicate that we do not know the end address.

avogelsgesang marked 2 inline comments as done.

Adjust comments as suggested by @MaskRay

Update one more comment based on @MaskRay's comment that PT_GNU_EH_FRAME is not a section

libunwind/src/AddressSpace.hpp
640

Thanks for catching this! :)

653

Sounds good to me. I updated the comment accordingly

@MaskRay Do you have any additional comments/requests? Or am I good to land this patch?

MaskRay accepted this revision.Aug 9 2022, 2:27 PM
MaskRay added inline comments.
libunwind/src/AddressSpace.hpp
614

PT_GNU_EH_FRAME is not a section.

"return PT_GNU_EH_FRAME" should suffice.

640

End full sentences with a period.

small comment fixes

avogelsgesang marked 3 inline comments as done.Aug 9 2022, 3:53 PM
This revision was landed with ongoing or failed builds.Aug 9 2022, 4:19 PM
This revision was automatically updated to reflect the committed changes.
mgorny added a subscriber: mgorny.Sep 13 2022, 11:52 PM

I'm sorry to be reporting it this late but this change broke building on 32-bit x86: https://github.com/llvm/llvm-project/issues/57733