Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add -Z no-unique-section-names to reduce ELF header bloat. #89581

Merged
merged 1 commit into from
Oct 26, 2021

Conversation

jblazquez
Copy link
Contributor

@jblazquez jblazquez commented Oct 5, 2021

This change adds a new compiler flag that can help reduce the size of ELF binaries that contain many functions.

By default, when enabling function sections (which is the default for most targets), the LLVM backend will generate different section names for each function. For example, a function func would generate a section called .text.func. Normally this is fine because the linker will merge all those sections into a single one in the binary. However, starting with LLVM 12, the backend will also generate unique section names for exception handling, resulting in thousands of .gcc_except_table.* sections ending up in the final binary because some linkers like LLD don't currently merge or strip these EH sections (see discussion here). This can bloat the ELF headers and string table significantly in binaries that contain many functions.

The new option is analogous to Clang's -fno-unique-section-names, and instructs LLVM to generate the same .text and .gcc_except_table section for each function, resulting in a smaller final binary.

The motivation to add this new option was because we have a binary that ended up with so many ELF sections (over 65,000) that it broke some existing ELF tools, which couldn't handle so many sections.

Here's our old binary:

$ readelf --sections old.elf | head -1
There are 71746 section headers, starting at offset 0x2a246508:

$ readelf --sections old.elf | grep shstrtab
  [71742] .shstrtab      STRTAB          0000000000000000 2977204c ad44bb 00      0   0  1

That's an 11MB+ string table. Here's the new binary using this option:

$ readelf --sections new.elf | head -1
There are 43 section headers, starting at offset 0x29143ca8:

$ readelf --sections new.elf | grep shstrtab
  [40] .shstrtab         STRTAB          0000000000000000 29143acc 0001db 00      0   0  1

The whole binary size went down by over 20MB, which is quite significant.

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @cjgillot (or someone else) soon.

Please see the contribution instructions for more information.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Oct 5, 2021
@@ -1188,6 +1188,8 @@ options! {
"compile without linking"),
no_parallel_llvm: bool = (false, parse_no_flag, [UNTRACKED],
"run LLVM in non-parallel mode (while keeping codegen-units and ThinLTO)"),
no_unique_section_names: bool = (false, parse_bool, [TRACKED],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't use parse_no_flag - unlike the other no_* flags above - because the parse_no_flag function says its use is deprecated, so I made it a plain bool. Also, not entirely sure what tracking is for, so I set it to [TRACKED] because function_sections is also tracked.

@Mark-Simulacrum
Copy link
Member

This seems pretty reasonable. Is there a reason we shouldn't enable this by default? It seems like the general approach should be that if it makes your binary smaller, since it prevents dead section elimination but otherwise is beneficial. Is that right?

It would be good to add a page to the unstable book in this PR https://github.com/rust-lang/rust/tree/master/src/doc/unstable-book/src/compiler-flags with some appropriate links and details on this.

@jblazquez
Copy link
Contributor Author

jblazquez commented Oct 6, 2021

I'm basing the defaults on what Clang does, and wasn't comfortable changing the default behavior for all ELF targets.

Clang (and LLVM) default to -funique-section-names (that is, do generate unique section names), presumably because if you use a .gcc_except_table-aware linker then you can get smaller binaries when passing it --gc-sections, since the linker understands it can remove the unreferenced EH sections of the stripped functions as well.

So to clarify, my understanding is that if you use the GNU ld linker, you want unique section names so --gc-sections makes the binary smaller. But if you use another linker, you want non-unique sections names to make the ELF headers smaller.

Do you think rustc should deviate from Clang here and default to -fno-unique-section-names?

@Mark-Simulacrum
Copy link
Member

I don't think we should change the behavior in this PR, mostly wanted to get a read on whether it would make sense to in the future.

@jblazquez
Copy link
Contributor Author

Sounds good. I'll get working on the unstable book page.

This change adds a new compiler flag that can help reduce the size of
ELF binaries that contain many functions.

By default, when enabling function sections (which is the default for most
targets), the LLVM backend will generate different section names for each
function. For example, a function "func" would generate a section called
".text.func". Normally this is fine because the linker will merge all those
sections into a single one in the binary. However, starting with LLVM 12
(llvm/llvm-project@ee5d1a0), the backend will
also generate unique section names for exception handling, resulting in
thousands of ".gcc_except_table.*" sections ending up in the final binary
because some linkers don't currently merge or strip these EH sections.
This can bloat the ELF headers and string table significantly in
binaries that contain many functions.

The new option is analogous to Clang's -fno-unique-section-names, and
instructs LLVM to generate the same ".text" and ".gcc_except_table"
section for each function, resulting in smaller object files and
potentially a smaller final binary.
@jblazquez
Copy link
Contributor Author

I added documentation for the new flag.

@apiraino apiraino added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Oct 14, 2021
@cjgillot
Copy link
Contributor

r? @Mark-Simulacrum

@Mark-Simulacrum
Copy link
Member

Mark-Simulacrum commented Oct 24, 2021

Thanks. This looks basically good to go -- could you file a tracking issue (just a regular issue with a pointer to the future docs and a link to this PR is good) and list it in the unstable book entry?

r=me with that done (please use @rustbot ready to update labels)

@Mark-Simulacrum Mark-Simulacrum added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 24, 2021
@jblazquez
Copy link
Contributor Author

Thanks Mark, I created the issue here: #90275

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Oct 25, 2021
@Mark-Simulacrum
Copy link
Member

@bors r+

@bors
Copy link
Contributor

bors commented Oct 25, 2021

📌 Commit 4ed846a has been approved by Mark-Simulacrum

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 25, 2021
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Oct 25, 2021
Add -Z no-unique-section-names to reduce ELF header bloat.

This change adds a new compiler flag that can help reduce the size of ELF binaries that contain many functions.

By default, when enabling function sections (which is the default for most targets), the LLVM backend will generate different section names for each function. For example, a function `func` would generate a section called `.text.func`. Normally this is fine because the linker will merge all those sections into a single one in the binary. However, starting with [LLVM 12](llvm/llvm-project@ee5d1a04), the backend will also generate unique section names for exception handling, resulting in thousands of `.gcc_except_table.*` sections ending up in the final binary because some linkers like LLD don't currently merge or strip these EH sections (see discussion [here](https://reviews.llvm.org/D83655)). This can bloat the ELF headers and string table significantly in binaries that contain many functions.

The new option is analogous to Clang's `-fno-unique-section-names`, and instructs LLVM to generate the same `.text` and `.gcc_except_table` section for each function, resulting in a smaller final binary.

The motivation to add this new option was because we have a binary that ended up with so many ELF sections (over 65,000) that it broke some existing ELF tools, which couldn't handle so many sections.

Here's our old binary:

```
$ readelf --sections old.elf | head -1
There are 71746 section headers, starting at offset 0x2a246508:

$ readelf --sections old.elf | grep shstrtab
  [71742] .shstrtab      STRTAB          0000000000000000 2977204c ad44bb 00      0   0  1
```

That's an 11MB+ string table. Here's the new binary using this option:

```
$ readelf --sections new.elf | head -1
There are 43 section headers, starting at offset 0x29143ca8:

$ readelf --sections new.elf | grep shstrtab
  [40] .shstrtab         STRTAB          0000000000000000 29143acc 0001db 00      0   0  1
```

The whole binary size went down by over 20MB, which is quite significant.
bors added a commit to rust-lang-ci/rust that referenced this pull request Oct 25, 2021
…askrgr

Rollup of 4 pull requests

Successful merges:

 - rust-lang#89581 (Add -Z no-unique-section-names to reduce ELF header bloat.)
 - rust-lang#90196 (Fix and extent ControlFlow `traverse_inorder` example)
 - rust-lang#90255 (:arrow_up: rust-analyzer)
 - rust-lang#90266 (Prevent duplicate caller bounds candidates by exposing default substs in Unevaluated)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 2f67647 into rust-lang:master Oct 26, 2021
@rustbot rustbot added this to the 1.58.0 milestone Oct 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants