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

Generate synthetic object file to ensure all exported and used symbols participate in the linking #95604

Merged
merged 11 commits into from
Apr 25, 2022

Conversation

nbdd0121
Copy link
Contributor

@nbdd0121 nbdd0121 commented Apr 2, 2022

Fix #50007 and #47384

This is the synthetic object file approach that I described in #95363 (comment), allowing all exported and used symbols to be linked while still allowing them to be GCed.

Related #93791, #95363

r? @petrochenkov
cc @carbotaniuman

@rustbot rustbot added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Apr 2, 2022
@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 2, 2022
@petrochenkov
Copy link
Contributor

One reason why I like this approach more than #95363 is that I have no idea how widely linker script files like forcelink.ld from #95363 are supported.
LLD, for example, had issues with supporting version script files used for exporting symbols on PE/COFF targets.

A "root" object file, on another hand, is as portable as it gets, with the same logic being reusable for very different targets.

@rust-log-analyzer

This comment has been minimized.

@carbotaniuman
Copy link
Contributor

Yeah this seems like a cleaner approach. This should fix the linkme style (test untested on this branch) of test cases as well as the linker-script style ones.

This doesn't fix #47384 fully however as it still requires #[used(linker)], but the combination of both works for all available examples.

@nbdd0121
Copy link
Contributor Author

nbdd0121 commented Apr 3, 2022

Yeah this seems like a cleaner approach. This should fix the linkme style (test untested on this branch) of test cases as well as the linker-script style ones.

This doesn't fix #47384 fully however as it still requires #[used(linker)], but the combination of both works for all available examples.

What do you mean? This certainly fixes the issue described by the OP in #47384.

@carbotaniuman
Copy link
Contributor

@nbdd0121

What do you mean? This certainly fixes the issue described by the OP in #47384.

It doesn't fix the issue in rust-audit. #47384 has like 4 different issues bundled into it, which makes testing quote difficult.

@nbdd0121
Copy link
Contributor Author

nbdd0121 commented Apr 3, 2022

Ah, the rust-audit case does require a custom linker script or #[used(linker)], but I think it's rightfully so.

@rust-log-analyzer

This comment has been minimized.

@petrochenkov
Copy link
Contributor

petrochenkov commented Apr 9, 2022

#47384 discusses so many different things that I'd like to set some agreements on what specifically this PR is supposed to assume and fix.

  1. Do not change meaning of #[used].
    Whether #[used] defaults to #[used(compiler)] or #[used(linker)] is a matter for a separate PR, like Only compile #[used] as llvm.compiler.used for ELF targets #93718 or other.
    Right now #[used] defaults to #[used(compiler)], aka CodegenFnAttrFlags::USED, aka the symbols must exist in object files.

    This PR changes the meaning of #[used(compiler)] and makes it closer to #[used(linker)] by marking #[used(compiler)] symbols as exported and keeping them alive for longer, until the linker GC.

  2. Visibility of #[used(...)] symbols.
    RFC: #[used] static variables rfcs#2386 specifies that used is orthogonal to symbols visibility (exported vs everything else non-exported).

    This PR marks both #[used(compiler)] and #[used(linker)] symbols as exported, even if they wouldn't be exported without that, which contradicts the RFC.
    I'm not actually sure whether you can guaranteedly keep a non-exported symbol alive until linker GC, but for #[used(compiler)] symbols that's not necessary and they certainly can stay non-exported as stated in the RFC.

  3. Let's assume -C export-executable-symbols already exists.
    RFC: -C export-executable-symbols rfcs#2841 is an accepted feature, and the implementation seems to be in progress, so we just need to not forget to include it into the picture. Executables with -C export-executable-symbols may end up being identical to cdylibs in these regards though.

@petrochenkov
Copy link
Contributor

@nbdd0121
Could you actually split this PR into two?

  • The first PR is just keeping what is already exported, using symbols.o.
  • The second PR is for used-related changes.

I'll be able to approve the first PR almost immediately, and it will then unblock #95818.
Then we'll be able to discuss used in more detail.

@nbdd0121
Copy link
Contributor Author

nbdd0121 commented Apr 9, 2022

#47384 discusses so many different things that I'd like to set some agreements on what specifically this PR is supposed to assume and fix.

  1. Do not change meaning of #[used].
    Whether #[used] defaults to #[used(compiler)] or #[used(linker)] is a matter for a separate PR, like Only compile #[used] as llvm.compiler.used for ELF targets #93718 or other.
    Right now #[used] defaults to #[used(compiler)], aka CodegenFnAttrFlags::USED, aka the symbols must exist in object files.
    This PR changes the meaning of #[used(compiler)] and makes it closer to #[used(linker)] by marking #[used(compiler)] symbols as exported and keeping them alive for longer, until the linker GC.

No. This PR does not change the meaning of #[used(compiler)]. Instead, it fixes the existing bug to ensure that it conforms to the desired behaviour. The PR ensures that #[used(compiler)] reaches the linker, but it doesn't modify the linker behaviour.

  1. Visibility of #[used(...)] symbols.
    RFC: #[used] static variables rfcs#2386 specifies that used is orthogonal to symbols visibility (exported vs everything else non-exported).
    This PR marks both #[used(compiler)] and #[used(linker)] symbols as exported, even if they wouldn't be exported without that, which contradicts the RFC.

No. Neither of these are exported. The export symbol list is not changed at all.

  1. Let's assume -C export-executable-symbols already exists.
    RFC: -C export-executable-symbols rfcs#2841 is an accepted feature, and the implementation seems to be in progress, so we just need to not forget to include it into the picture. Executables with -C export-executable-symbols may end up being identical to cdylibs in these regards though.

See above.

@petrochenkov
Copy link
Contributor

Neither of these are exported. The export symbol list is not changed at all.

As I understand the used symbols get into the reachable set (the reachable.rs change), and from there to the exported set, because the exported set is seeded with reachable set (reachable_set -> reachable_non_generics -> exported_symbols ).

@nbdd0121
Copy link
Contributor Author

nbdd0121 commented Apr 9, 2022

The #[used] symbols would have export level "Rust" instead of "C" so they won't be exported for cdylib or binary.

@nbdd0121
Copy link
Contributor Author

nbdd0121 commented Apr 9, 2022

There is an "exported_symbols" query and a "exported_symbols" function in linker.rs that produces the final exported symbols. They are actually different things; the first contains all reachables, latter does a filter to the former to get an actual list for export.

@petrochenkov
Copy link
Contributor

The #[used] symbols would have export level "Rust".

Yes, but they shouldn't have any exported level at all (unless they are exported for other reasons unrelated to used).

the first contains all reachables, latter does a filter to the former to get an actual list for export.

The list of symbols to filter in exported_symbols2 is seeded with the result of exported_symbols1, and the filtering is threshhold-based, so the SymbolExportLevel::Rust symbols will still stay exported in dylibs.

@nbdd0121
Copy link
Contributor Author

nbdd0121 commented Apr 9, 2022

The #[used] symbols would have export level "Rust".

Yes, but they shouldn't have any exported level at all (unless they are exported for other reasons unrelated to used).

"Rust" level export will be exported in dylib anyway and we already export an awful lot of symbols in such case.

I don't see why A in

#[used]
static A: u32 = 1;

shouldn't be exported while A in

static A: u32 = 1;
#[inline]
pub fn foo() -> &u32 { &A }

should.

@nbdd0121
Copy link
Contributor Author

nbdd0121 commented Apr 9, 2022

In a sense I don't treat "Rust" export level as truly being exports; it's basically just an export level for "everything that might get used" and I view it as an implementation detail to make dylibs work.

@petrochenkov
Copy link
Contributor

petrochenkov commented Apr 9, 2022

Ok, point understood.

I agree that in practice it's not too important to prune such used symbols from dylib exports, and we can skip it if it helps to simplify the implementation or fix other more important issues sooner.

I'd still consider it as a rough edge of the implementation, because when spec / mental model say one thing and the implementation do another it often leads to misunderstanding and mess.
Could you add a FIXME comment to reachable.rs telling that after being marked as reachable used symbols will have SymbolExportLevel::Rust and may end up being exported from dylibs, but that's mostly ok?

@petrochenkov
Copy link
Contributor

The issue 1 (#[used(compiler)] symbols being made closer to #[used(linker)] due to being over-exposed from dylibs) is a consequence of the issue 2, so the comment from #95604 (comment) will address it as well.

@petrochenkov
Copy link
Contributor

Otherwise LGTM, no need to split into two PRs (#95604 (comment)) anymore.

@petrochenkov petrochenkov 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 Apr 9, 2022
bors added a commit to rust-lang-ci/rust that referenced this pull request May 13, 2022
Fix e_flags for 32-bit MIPS targets in generated object file

In rust-lang#95604 the compiler started generating a temporary symbols.o which is added to the linker invocation. This object file has an `e_flags` which is invalid for 32-bit MIPS targets. Even though symbols.o doesn't contain code, linking these targets with [lld fails](https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/MipsArchTree.cpp#L76-L79) with
```
rust-lld: error: foo-cgu.0.rcgu.o: ABI 'o32' is incompatible with target ABI 'n64'
```
because it omits the ABI bits (`EF_MIPS_ABI_O32`) so lld assumes it's using the N64 ABI. This breaks linking on nightly for the out-of-tree [mipsel-sony-psx target](ayrtonm/psx-sdk-rs#9), the builtin mipsel-sony-psp target (cc `@overdrivenpotato)` and probably any other 32-bit MIPS target using lld.

This PR sets the ABI in `e_flags` to O32 since that's the only ABI for 32-bit MIPS that LLVM supports. It also sets other `e_flags` bits based on the target to avoid similar issues with the object file arch and PIC. I had to bump the object crate version since some of these constants were [added recently](gimli-rs/object#433). I'm not sure if this PR needs a test, but I can confirm that it fixes the linking issue on both targets I mentioned.
yvt added a commit to r3-os/r3 that referenced this pull request May 23, 2022
Fixes linker errors in an application that is linked to `r3_port_riscv`
and does not use the `riscv-rt`-compatible startup routine provided by
`r3_port_riscv::use_rt!`.

Rust implements `rlib`s as static libraries (archives). Linkers treat
archives differently from object files: all object files participate in
linking, while archives will only participate in linking if they can
satisfy at least one undefined reference (version scripts don't count).
This means that in an application that does not use `use_rt!`,
`libriscv_rt*.rlib` does not participate in linking at all. This
behavior also causes `#[no_mangle]` and `#[used]` items to be ignored by
the linker (`KEEP` in linker scripts can't keep them either), leading to
a long-standing bug in Rust ([rust-lang/rust#47384][2]).

The situation changed with the merge of [rust-lang/rust#95604][1]. To
fix [rust-lang/rust#47384][2], this PR introduced a synthetic object
file containing references to all symbols pertaining to `#[no_mangle]`
and `#[used]` items. `libriscv_rt*.rlib` now participates in linking,
but the expectation is that `--gc-sections` will remove unused items in
the end, unless they are explicitly retained by `KEEP` in linker scripts
or other means.

This change unexpectedly caused breakage in the tests for `qemu_sifive_u
_s_rv(64,32)` targets, which use a custom startup routine instead of
`use_rt!`. For some reason, the linker didn't respect `--gc-sections`
for some items from `libriscv_rt*.rlib` and decided to include them in
the output binary. These items reference symbols that are defined by
the `riscv-rt` linker script, which isn't used by `qemu_sifive_u_s_
rv(64,32)` targets, hence the linker error.

The thing is, `riscv-rt` outputs these items in the `.init` section[3],
which is one of the section names hard-coded in LLD[4] to be excluded
from `--gc-sections`. As a result, these items were considered for
inclusion despite being referenced from nowhere.

This commit works around this problem by making `r3_port_riscv`'s
`riscv-rt` dependency optional, ensuring that `riscv-rt` participates
in linking only if needed by the application.

[1]: rust-lang/rust#95604
[2]: rust-lang/rust#47384
[3]: https://github.com/rust-embedded/riscv-rt/blob/7de3d2744a465ad723519c04f13c56664e138cb9/asm.S#L20
[4]: https://github.com/llvm/llvm-project/blob/b86440ecde5c1dec5b898a3f1bc08ab9853d5ed9/lld/ELF/MarkLive.cpp#L183
yvt added a commit to r3-os/r3 that referenced this pull request May 24, 2022
Fixes linker errors in an application that is linked to `r3_port_riscv`
and does not use the `riscv-rt`-compatible startup routine provided by
`r3_port_riscv::use_rt!`.

Rust implements `rlib`s as static libraries (archives). Linkers treat
archives differently from object files: all object files participate in
linking, while archives will only participate in linking if they can
satisfy at least one undefined reference (version scripts don't count).
This means that in an application that does not use `use_rt!`,
`libriscv_rt*.rlib` does not participate in linking at all. This
behavior also causes `#[no_mangle]` and `#[used]` items to be ignored by
the linker (`KEEP` in linker scripts can't keep them either), leading to
a long-standing bug in Rust ([rust-lang/rust#47384][2]).

The situation changed with the merge of [rust-lang/rust#95604][1]. To
fix [rust-lang/rust#47384][2], this PR introduced a synthetic object
file containing references to all symbols pertaining to `#[no_mangle]`
and `#[used]` items. `libriscv_rt*.rlib` now participates in linking,
but the expectation is that `--gc-sections` will remove unused items in
the end, unless they are explicitly retained by `KEEP` in linker scripts
or other means.

This change unexpectedly caused breakage in the tests for `qemu_sifive_u
_s_rv(64,32)` targets, which use a custom startup routine instead of
`use_rt!`. For some reason, the linker didn't respect `--gc-sections`
for some items from `libriscv_rt*.rlib` and decided to include them in
the output binary. These items reference symbols that are defined by
the `riscv-rt` linker script, which isn't used by `qemu_sifive_u_s_
rv(64,32)` targets, hence the linker error.

The thing is, `riscv-rt` outputs these items in the `.init` section[3],
which is one of the section names hard-coded in LLD[4] to be excluded
from `--gc-sections`. As a result, these items were considered for
inclusion despite being referenced from nowhere.

This commit works around this problem by making `r3_port_riscv`'s
`riscv-rt` dependency optional, ensuring that `riscv-rt` participates
in linking only if needed by the application.

P.S. An exclamation mark (`!`) in the commit headline will be used to
indicate breaking changes from now on, as per [Conventional Commits
1.0.0][5].

[1]: rust-lang/rust#95604
[2]: rust-lang/rust#47384
[3]: https://github.com/rust-embedded/riscv-rt/blob/7de3d2744a465ad723519c04f13c56664e138cb9/asm.S#L20
[4]: https://github.com/llvm/llvm-project/blob/b86440ecde5c1dec5b898a3f1bc08ab9853d5ed9/lld/ELF/MarkLive.cpp#L183
[5]: https://www.conventionalcommits.org/en/v1.0.0/
yvt added a commit to r3-os/r3 that referenced this pull request May 24, 2022
Fixes linker errors in an application that is linked to `r3_port_riscv`
and does not use the `riscv-rt`-compatible startup routine provided by
`r3_port_riscv::use_rt!`.

Rust implements `rlib`s as static libraries (archives). Linkers treat
archives differently from object files: all object files participate in
linking, while archives will only participate in linking if they can
satisfy at least one undefined reference (version scripts don't count).
This means that in an application that does not use `use_rt!`,
`libriscv_rt*.rlib` does not participate in linking at all. This
behavior also causes `#[no_mangle]` and `#[used]` items to be ignored by
the linker (`KEEP` in linker scripts can't keep them either), leading to
a long-standing bug in Rust ([rust-lang/rust#47384][2]).

The situation changed with the merge of [rust-lang/rust#95604][1]. To
fix [rust-lang/rust#47384][2], this PR introduced a synthetic object
file containing references to all symbols pertaining to `#[no_mangle]`
and `#[used]` items. `libriscv_rt*.rlib` now participates in linking,
but the expectation is that `--gc-sections` will remove unused items in
the end, unless they are explicitly retained by `KEEP` in linker scripts
or other means.

This change unexpectedly caused breakage in the tests for `qemu_sifive_u
_s_rv(64,32)` targets, which use a custom startup routine instead of
`use_rt!`. For some reason, the linker didn't respect `--gc-sections`
for some items from `libriscv_rt*.rlib` and decided to include them in
the output binary. These items reference symbols that are defined by
the `riscv-rt` linker script, which isn't used by `qemu_sifive_u_s_
rv(64,32)` targets, hence the linker error.

The thing is, `riscv-rt` outputs these items in the `.init` section[3],
which is one of the section names hard-coded in LLD[4] to be excluded
from `--gc-sections`. As a result, these items were considered for
inclusion despite being referenced from nowhere.

This commit works around this problem by making `r3_port_riscv`'s
`riscv-rt` dependency optional, ensuring that `riscv-rt` participates
in linking only if needed by the application.

P.S. I'll try to use an exclamation mark (`!`) in the commit headline
to indicate breaking changes from now on, as per [Conventional Commits
1.0.0][5].

[1]: rust-lang/rust#95604
[2]: rust-lang/rust#47384
[3]: https://github.com/rust-embedded/riscv-rt/blob/7de3d2744a465ad723519c04f13c56664e138cb9/asm.S#L20
[4]: https://github.com/llvm/llvm-project/blob/b86440ecde5c1dec5b898a3f1bc08ab9853d5ed9/lld/ELF/MarkLive.cpp#L183
[5]: https://www.conventionalcommits.org/en/v1.0.0/
xSetech added a commit to xSetech/rust that referenced this pull request Jul 9, 2023
PR rust-lang#95604 introduced a "synthetic object file to ensure all exported and
used symbols participate in the linking". One constraint on this file is
that for MIPS-based targets, its architecture-specific ELF flags must be
the same as all other object files passed to the linker. That's enforced
by LLD, here:
https://github.com/llvm/llvm-project/blob/llvmorg-16.0.6/lld/ELF/Arch/MipsArchTree.cpp#L77

The current approach to determining e_flags for 32-bit was implemented
in PR rust-lang#96930, which links to this issue that summarizes the problem well:
ayrtonm/psx-sdk-rs#9

> ... the temporary object file is created with an e_flags which is
> invalid for 32-bit MIPS targets. The main issue is that it omits the ABI
> bits (EF_MIPS_ABI_O32) which implies it uses the N64 ABI.

To enable the N32 MIPS ABI (which succeeded O32), this patch enables
setting the synthetic object's ABI based on the target "llvm-abiname"
field, if it's given; otherwise, the O32 ABI is assumed for 32-bit MIPS
targets.

More information about the N32 ABI can be found here:
https://web.archive.org/web/20160121005457/http://techpubs.sgi.com/library/manuals/2000/007-2816-005/pdf/007-2816-005.pdf
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Jul 11, 2023
Support explicit 32-bit MIPS ABI for the synthetic object

PR rust-lang#95604 introduced a "synthetic object file to ensure all exported and used symbols participate in the linking". One constraint on this file is that for MIPS-based targets, its architecture-specific ELF flags must be the same as all other object files passed to the linker. That's enforced by LLD, here:
https://github.com/llvm/llvm-project/blob/llvmorg-16.0.6/lld/ELF/Arch/MipsArchTree.cpp#L77

The current approach to determining e_flags for 32-bit was implemented in PR rust-lang#96930, which links to this issue that summarizes the problem well: ayrtonm/psx-sdk-rs#9

> ... the temporary object file is created with an e_flags which is
> invalid for 32-bit MIPS targets. The main issue is that it omits the ABI
> bits (EF_MIPS_ABI_O32) which implies it uses the N64 ABI.

To enable the N32 MIPS ABI (which succeeded O32), this patch enables setting the synthetic object's ABI based on the target "llvm-abiname" field, if it's given; otherwise, the O32 ABI is assumed for 32-bit MIPS targets.

More information about the N32 ABI can be found here: https://web.archive.org/web/20160121005457/http://techpubs.sgi.com/library/manuals/2000/007-2816-005/pdf/007-2816-005.pdf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. perf-regression Performance regression. 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.

Compiling to Shared Library doesn't always expose reexported symbols