This is an archive of the discontinued LLVM Phabricator instance.

[lld-macho] create __TEXT,__unwind_info from __LD,__compact_unwind
ClosedPublic

Authored by gkm on Aug 28 2020, 12:43 PM.

Details

Summary

Digest the input __LD,__compact_unwind and produce the output __TEXT,__unwind_info. This is the initial commit with the major functionality.

Successor commits will add handling for ...

  • __TEXT,__eh_frame
  • personalities & LSDA
  • -r pass-through

Diff Detail

Unit TestsFailed

Event Timeline

gkm created this revision.Aug 28 2020, 12:43 PM
gkm requested review of this revision.Aug 28 2020, 12:43 PM
gkm edited the summary of this revision. (Show Details)Aug 28 2020, 12:54 PM
gkm updated this revision to Diff 288738.Aug 28 2020, 5:42 PM
  • Relocate some macro definitions into libunwind/include/mach-o/compact_unwind_encoding.h
  • Decouple target word size from the host word size for the compact unwind entry structs
Herald added a project: Restricted Project. · View Herald TranscriptAug 28 2020, 5:42 PM
Herald added a reviewer: Restricted Project. · View Herald Transcript
int3 added a comment.Aug 28 2020, 8:03 PM

just a partial review for now... will come back and do a deeper reading next week after I understand a bit more about the CU format.

lld/MachO/OutputSegment.cpp
54

nit: StringRefs can be compared using ==

lld/MachO/UnwindInfo.cpp
2

missing license header

59

I think this should be isNeeded(), not isHidden() -- "hidden" indicates that the section header is absent but that the section data itself should still be emitted

94

nit: targetIsec would be clearer

140

frequencies

lld/MachO/UnwindInfo.h
52–67

why the need to mark everything as mutable?

lld/MachO/Writer.cpp
413–414

This seems a bit hacky. Do we really have to put the __compact_unwind sections in an OutputSegment? If we're not going to output them, could we just put all these InputSections in a vector contained in UnwindInfoSection?

Also, we may want to support emitting object files at some point via -r, in which case we'll actually want to emit the __LD segment at a valid position.

lld/test/MachO/tools/validate-unwind-info.py
90

this seems unnecessary given that we'll be exiting anyway when this function returns

gkm marked 8 inline comments as done.Aug 29 2020, 1:48 AM
gkm added inline comments.
lld/MachO/UnwindInfo.h
52–67

The work of morphing __LD,__compact_unwind into __TEXT,__unwind_info happens in uint64_t getSize() const and void writeTo(uint8_t *buf) const, which can only be logically const, but not physically.

lld/MachO/Writer.cpp
413–414

I agree that it could use some improvement. I will revisit.

Behaviorally, __LD,__compact_unwind is a MergedOutputSegment, except with different timing and destination: we relocate & write it to a temp buffer from Writer::assignAddresses()) rather than writing to the final link output from Writer::writeSections(). We need to relocate it fully in order to compute the size of the __TEXT,__unwind_info section when laying-out sections & binding to addresses.

You are quite right regarding -r, which makes __LD,__compact_unwind a completely normal MergedOutputSegment.

lld/test/MachO/tools/validate-unwind-info.py
90

True, but since I explicitly sys.exit("... diagnostic ...") on error, for symmetry I chose to explicitly sys.exit() on success. I dislike falling through the floor of main, despite language guarantees, and since sys.exit() accepts a string arg on error, I prefer that to using numeric return STATUS.

gkm updated this revision to Diff 288839.Aug 29 2020, 11:23 PM
gkm marked 3 inline comments as done.

Update according to Jez's simple review-feedback items - i.e., everything except the hackiness surrounding __LD,__compact_unwind's anomalies as a MergedOutputSection.

gkm updated this revision to Diff 288867.Aug 30 2020, 12:55 PM
  • Rework pageBounds as a simple vector rather than a vector of pairs, and expand associated comments
gkm updated this revision to Diff 288873.Aug 30 2020, 2:21 PM
  • s/std::max/std::min/
  • Revert to simple llvm-lit test format, since bash is unnecessary
gkm updated this revision to Diff 288874.Aug 30 2020, 2:44 PM
  • pass functionAddressMax via lambda capture rather than via constructed cuEntry
  • heed clang-tidy's advice
gkm updated this revision to Diff 288875.Aug 30 2020, 3:16 PM

Cleanups:

  • s/totalCuEntries/cuCount/
  • constify all CompactUnwindEntry64*
  • simplify level-1 index sentinel address calculation
gkm updated this revision to Diff 288877.Aug 30 2020, 4:16 PM
  • Correction in lld/MachO/UnwindInfo.h: s/LLD_MACHO_COMPACT_UNWIND_H/LLD_MACHO_UNWIND_INFO_H/
  • Move some constant definitions that belong in libunwind/include/mach-o/compact_unwind_encoding.h
gkm updated this revision to Diff 288994.Aug 31 2020, 11:23 AM
  • Revise the arg signature for the comparison lambda for std::lower_bound() to emphasize that the 2nd pointer argument is ignored. Rather, we pass the comparand via lambda capture.
gkm updated this revision to Diff 289061.EditedAug 31 2020, 7:13 PM

Improvements & cleanups to generate-cfi-funcs.py:

  • add --functions=N arg
  • move some comments into doc strings
gkm updated this revision to Diff 289338.Sep 1 2020, 6:14 PM

Improvements & cleanups to validate-unwind-info.py:

  • accept input from multiple files or stdin
  • fail if any expected categories of input are absent
int3 added inline comments.Sep 1 2020, 8:21 PM
lld/MachO/UnwindInfo.h
52–67

I would much prefer if we could have a method that explicitly computes the unwind info... but first let me see if I understand the constraints here.

From what I understand, the size of the unwind info depends on the values of CompactUnwindEntry64::functionAddress, or more precisely, their relative offsets. So we need to assign addresses to our functions before computing the unwind info size. After addresses are assigned, the cuSection->writeTo() call below will update the functionAddress entries to point to the final addresses, after which we can compute the needed size. Does all that sound right?

From what I can tell, ld64 calculates unwind info size based on "tentative addresses", which I think have the right relative offsets, though their final values have yet to be fixed. I wonder if we could do something similar with our current design...

int3 added inline comments.Sep 1 2020, 8:24 PM
lld/MachO/UnwindInfo.h
52–67

Also -- do you know if it's possible for unwind info to point to functions that reside in sections other than __text?

gkm added inline comments.Sep 2 2020, 12:05 AM
lld/MachO/UnwindInfo.h
52–67

Yes, that's substantially correct, and I will elaborate:

UnwindInfoSection::getSize() must process the compact unwind entries and partition them into 4 KiB second-level pages in order to determine the final size of the __TEXT,__unwind_info. There are three factors that influence layout & therefore size:

  1. A maximum of 1021 entries can fit into a 4 KiB page
  2. The difference between address of first and last functions in a second-level page must be less than 2^24. The page will be cut short and remain under-filled when a candidate entry's 24-bit relative offset field would overflow.
  3. We do a linear scan and fold adjacent entries that have the same encoding, thus laying-out fewer entries in the output than are present in the input.

As currently implemented, I use mutable data members to cache the necessary and substantial work done in UnwindInfoSection::getSize() const so that UnwindInfoSection::writeTo() const need not redo it.

I believe it is not strictly necessary to relocate the compact unwind entries in getSize(). I can probably work with the raw input sections for page partitioning and entry folding, though the code will be trickier, and I won't escape the need to relocate later in writeTo() for sake of LSDA and personality addresses.

I chose to use data members as cache, to relocate early, and to use mutable to work-around constness of getSize() and writeTo() because that results in code that does things in the proper sequence, and in the most straightforward manner for sake of clarity and efficiency.

52–67

With assembler language, anything is possible. I assume one can place compiled functions in alternate sections via attributes or pragmas. Those sections might be discontiguous with __text, and therefore require lay-out in to distinct second-level pages. I don't know if ld64 supports non-__text compact unwind. I will need to conjure some test cases to see how it behaves. If ld64 doesn't handle non-__text, then we won't either. If it does, then I will add that later.

gkm updated this revision to Diff 289583.Sep 2 2020, 3:18 PM

lld/MachO/UnwindInfo.cpp:

  • Remove redundant unwind_info_section_header_lsda_entry, and use existing unwind_info_section_header_lsda_index_entry instead.
  • Don't cache UnwindInfoSection::isNeeded() result since it is only called once.

lld/test/MachO/tools/generate-cfi-funcs.py:

  • Permute the register save set in order to generate a larger variety of encodings.
  • Align generated functions at larger boundary so it works for testing ld64 also.

lld/test/MachO/tools/validate-unwind-info.py

  • Expand the object-file encoding regexp to capture optional personality+lsda
gkm edited the summary of this revision. (Show Details)Sep 2 2020, 10:01 PM
int3 added inline comments.Sep 3 2020, 2:57 PM
lld/MachO/UnwindInfo.h
52–67

I believe it is not strictly necessary to relocate the compact unwind entries in getSize(). I can probably work with the raw input sections for page partitioning and entry folding

How about doing most of what's currently in getSize() in a finalizeContents() method instead, much like what is currently being done for the the __LINKEDIT sections? My main concern is -- aside from a mutating method being marked as const -- is that compact unwind's getSize() has an implicit dependency on the addresses of another section. Other synthetic sections' finalizeContent methods have such a dependency, too, so this would fit the mold.

Of course, unlike the __LINKEDIT sections, the address assignments of later sections do depend on compact unwind's size, so we'll have to call it in the middle of assignAddresses. Such special-casing of CW would be a bit more verbose than what we currently have, but I think it's a good thing to make the uniqueness of compact unwind's requirements explicit.

gkm updated this revision to Diff 290105.Sep 5 2020, 12:19 PM

Do the work in finalize() member rather than getSize() const. Drop mutable from data members. Ahhh! So much nicer!

gkm updated this revision to Diff 290114.Sep 5 2020, 5:32 PM
  • use %python prefix in test file
  • minor cleanups
int3 added a comment.Sep 8 2020, 10:56 AM

Having things in finalize() indeed looks a lot cleaner :D

lld/MachO/SyntheticSections.h
22

this include doesn't seem necessary

lld/MachO/UnwindInfo.cpp
92–93

is the Twine() call here necessary? I would expect the concatenated StringRefs to already be a Twine

108

ditto, avoid <map>

121

codebase convention is to avoid curly braces for one-liner blocks

128

frequencies

148

successive

151

pageBounds

156

within

157–159

nit 1: the type here is pretty verbose, a type alias would be nice (auto would be acceptable here too I think)

nit 2: it0 and itN aren't the most descriptive names... something like intervalStart and intervalMax would be nice

168–169

why not pass functionAddressMax in as the 3rd parameter to lower_bound?

edit: oh, because the return type isn't the container element type. would be good to have a comment :)

194–197

can be rm'ed

212

would be good to have a comment to the effect that there are a bunch of integer arrays immediately after the section header (I had to pause and think about what &uip[1] meant)

258

what does a kind of 3 indicate?

lld/MachO/UnwindInfo.h
50

try to avoid <map> if possible: https://llvm.org/docs/ProgrammersManual.html#map

also the key here should be compact_unwind_encoding_t to be consistent :)

51

nit: I'd prefer structs over pairs for readable field names

lld/MachO/Writer.cpp
413–414

I think it might be worthwhile to duplicate some of the code in InputSection::writeTo to specialize it for the compact unwind case. The current writeTo code assumes that both the source and target sections referenced by a relocation have their final output addresses determined. But this is not true for the compact unwind section -- it doesn't have a valid output address. It just happens to work because there are no pcrel relocations involved. We should really error out if we see a pcrel relocation while relocating __compact_unwind.

Creating a separate compact unwind relocating method would free us from having to create Output{Segments, Sections} that we discard later. I'm hoping we can retain the invariant where we add OutputSections to OutputSegments only after we have determined that they are needed.

lld/test/MachO/compact-unwind.test
4–6

My understanding of the need for generate-cfi-funcs.py is that some test cases need to be very large in order to exercise the edge cases in the implementation. In addition to providing that functionality, this script also serves as a fuzzer by generating random test cases. Would it be possible for the unit test to instead enumerate a small number of cases that cover all important code paths, in order that we run fewer test cases?

int3 added inline comments.Sep 8 2020, 5:15 PM
lld/MachO/UnwindInfo.cpp
91–93

Is this check really necessary? What kind of errors are we defending against? I would rather we not loop over the relocations unless necessary (for performance)

96–97

CU relocations can be section-based. Just checked a simple program:

~/tmp: llvm-readobj --relocations --expand-relocs bar.o

File: bar.o
Format: Mach-O 64-bit x86-64
Arch: x86_64
AddressSize: 64bit
Relocations [
  Section __compact_unwind {
    Relocation {
      Offset: 0x0
      PCRel: 0
      Length: 3
      Type: X86_64_RELOC_UNSIGNED (0)
      Section: __text (1)
    }
  }
]
~/tmp: cat bar.cpp
int foo() {
  return 123;
}
smeenai added inline comments.Sep 8 2020, 5:17 PM
lld/MachO/UnwindInfo.cpp
91–93

Yup. I don't have the full context here yet, but https://lld.llvm.org/NewLLD.html#numbers-you-want-to-know is relevant.

gkm marked 18 inline comments as done.Sep 9 2020, 5:55 PM
gkm added inline comments.
lld/MachO/UnwindInfo.cpp
91–93

This is an assertion--a sanity check on my own assumptions. Once I tested more widely, I will have no further use for it. A TODO comment to convey my intention is appropriate.

92–93

Regarding Twine(): I meant to only pass the initial string constant so that later + operands would be chained to it. I did not mean to pass entire concat expression. I used explicit Twine() following Saleem's example in an earlier diff. I'm happy to drop it. I see that diag functions already coerce string constants to Twine anyway.

96–97

Yes. I expect relocs to all reference sections whose address is assigned, i.e., __TEXT,__text. The assertion checks if this is ever NOT the case.

168–169

I added a comment explaining the choice.

lld/MachO/UnwindInfo.h
51

commonEncodings works best as a vector of pairs because it is filled from a DenseMap iterator which returns precisely the pairs we desire.

lld/MachO/Writer.cpp
413–414

I still owe you an answer for this one ...

lld/test/MachO/compact-unwind.test
4–6

It is already a single test case, made deterministic by passing --seed' so that check-lld-macho` runs are consistent. The multiplicity that might be reduced is the number of functions generated for that one test case. However, since I need to test boundary conditions around breaking 1021-entry pages, my options are limited. If there is a way to prefer an installed llvm-mc over the locally built one, that would solve it.

gkm marked 5 inline comments as done.Sep 9 2020, 5:58 PM
gkm added inline comments.
lld/MachO/UnwindInfo.cpp
91–93

I am only looping over a subset of relocs, numbered 1+ per function. The "+" represents the subset of functions that have personality+lsda.

gkm updated this revision to Diff 290858.Sep 9 2020, 6:23 PM
  • follow review feedback
gkm updated this revision to Diff 291330.Sep 11 2020, 1:52 PM
  • finalize LD,compact_unwind before relocating
gkm updated this revision to Diff 291439.Sep 12 2020, 7:07 PM
  • Folding adjacent CU entries requires a vector with monotonically increasing functionAddress, and to get that we must first apply std::sort().
  • Abandon passing pageBreak 2nd compare arg via lambda-capture, because it is a fragile technique. It is only by an accident of the implementation that it works on std::lower_bound. It does not work for std::upper_bound, std::binary_search, or std::equal_range.
gkm added inline comments.Sep 17 2020, 2:56 PM
lld/MachO/Writer.cpp
413–414

I believe this latest rev works well and isn't so ugly. I retain the normal flow for gathering input sections and attaching them to MergedOutputSection for __LD, but I exclude it from outputSegments (without -r) so further downstream processing doesn't happen. I do insert __LD into nameToOutputSegment[] so that the synthetic UnwindInfoSection::finalize() can find it for processing. That seems minimally invasive and doesn't need a subclass.

All evidence I have seen shows that reloc types in __LD,__compact_unwind are always absolute and never PC-relative. The only output address that must be determined is lld's VMA. OutputSection::writeTo() accepts a pointer to a buffer, which is normally mapped to the linker's output file. However, when relocating __compact_unwind, the buffer is the internal std::vector<CompactUnwindEntry64>::data() rather than the output file.

gkm updated this revision to Diff 292632.Sep 17 2020, 2:59 PM
  • clean-up some ugly special cases by excluding __LD from outputSegments
gkm updated this revision to Diff 292643.Sep 17 2020, 3:36 PM
  • add UNWIND_INFO_COMMON_ENCODINGS_MAX
  • revise, expand & prune comments
  • improve variable names for CU entry folding
int3 accepted this revision.Sep 17 2020, 6:36 PM
int3 added inline comments.
lld/MachO/OutputSegment.cpp
73–74

Instead of filtering it out here, how about checking for __LD,__compact_unwind inside createOutputSections() while looping over mergedOutputSections, and then doing something like unwindInfoSection->setCompactUnwind(...)? Benefits: no need for the loop in UnwindInfoSection::isNeeded(), no need for getOutputSection, and no need to create an outputSegment to be discarded later.

That said, the current setup is definitely much cleaner than what we had before, and I'm happy to have this refinement in a follow-up diff

lld/MachO/UnwindInfo.cpp
202

s/adddress/addresses are/

289

nit: static_cast<size_t>

lld/MachO/UnwindInfo.h
15–18

nit: group includes with LLD headers first, then LLVM headers, then system headers

lld/MachO/Writer.cpp
425–426

how about using INT_MAX - 1 here instead, to cover the unlikely case where we have more than 100 sections? ld64 seems to do that

635

nit: Synthetic sections that only need to be used within Writer -- like SymtabSection -- can be a member of Writer instead of InStruct.

lld/test/MachO/compact-unwind.test
18

maybe change this seed to something that's not company-specific

lld/test/MachO/tools/validate-unwind-info.py
37

JFYI, the parens are unnecessary, but I'm fine if you prefer them

69

I don't suppose you meant to leave and False in?

gkm marked 10 inline comments as done.Sep 18 2020, 8:07 PM
gkm added inline comments.
lld/MachO/OutputSegment.cpp
73–74

No followup necessary. I did it all here.

gkm updated this revision to Diff 292936.Sep 18 2020, 8:32 PM
gkm marked an inline comment as done.
  • Final round of cleanups & review-feedback integration
gkm removed a reviewer: Restricted Project.Sep 18 2020, 8:33 PM
gkm removed a project: Restricted Project.
This revision is now accepted and ready to land.Sep 18 2020, 8:33 PM
int3 added inline comments.Sep 18 2020, 8:34 PM
lld/MachO/OutputSegment.cpp
73

is this still necessary?

gkm updated this revision to Diff 292937.Sep 18 2020, 8:48 PM
  • Remove redundant change to lld/MachO/OutputSegment.cpp
This revision was landed with ongoing or failed builds.Sep 18 2020, 10:02 PM
This revision was automatically updated to reflect the committed changes.
thakis added a subscriber: thakis.Sep 19 2020, 5:33 AM
thakis added inline comments.
lld/test/MachO/tools/generate-cfi-funcs.py
22

llvm still supports python2.7 for now.

thakis added inline comments.Sep 19 2020, 6:17 AM
lld/test/MachO/tools/generate-cfi-funcs.py
22

I went ahead and made the scripts 2.7-compatible in e22a4fd59de668af1cb943e23a6f4bfc93090e0f. Once we drop 2.7 support, it's hopefully easy to revert. Sorry for the trouble!