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

Allow specifying the ELF TLS ABI #132480

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

ilovepi
Copy link
Contributor

@ilovepi ilovepi commented Nov 2, 2024

TLSDESC is an alternate method of TLS access. It is part of the ELF TLS ABI, and is already commonly used in other toolchains. LLVM supports this for most ELF backends (X86_64, Aarch64, RISC-V). It has always been the default for Aarch64, but support for RISC-V and X86_64 were added before the last LLVM release.

More information on TLSDESC can be found in:
https://android.googlesource.com/platform/bionic/+/HEAD/docs/elf-tls.md or the original RFC:
https://www.fsfla.org/~lxoliva/writeups/TLS/RFC-TLSDESC-ARM.txt

This patch adds a new unstable option -Z tls-dialect, which matches the common -mtls-dialect= option used in Clang, GCC, and other toolchains. This option only needs to be passed through to LLVM to update the code generation. For targets like Aarch64 that always use TLSDESC, this has no effect. Eventually this flag should probably be stabilized since it is an important part of a program's ABI.

In the future, if LLVM changes its defaults for TLS to enable TLSDESC for a particular platform or architecture, users who do not wish to change can use the new option to select their desired TLS ABI.

TLSDESC is an alternate method of TLS access. It is part of the ELF TLS
ABI, and is already commonly used in other toolchains. LLVM supports
this for most ELF backends (X86_64, Aarch64, RISC-V). It has always been
the default for Aarch64, but support for RISC-V and X86_64 were added
before the last LLVM release.

More information on TLSDESC can be found in:
https://android.googlesource.com/platform/bionic/+/HEAD/docs/elf-tls.md
or the original RFC:
https://www.fsfla.org/~lxoliva/writeups/TLS/RFC-TLSDESC-ARM.txt

This patch adds a new unstable option `-Z tls-dialect`, which matches
the common `-mtls-dialect=` option used in Clang, GCC, and other
toolchains. This option only needs to be passed through to LLVM to
update the code generation. For targets like Aarch64 that always use
TLSDESC, this has no effect. Eventually this flag should probably be
stabilized since it is an important part of a program's ABI.

In the future, if LLVM changes its defaults for TLS to enable TLSDESC
for a particular platform or architecture, users who do not wish to
change can use the new option to select their desired TLS ABI.
@rustbot
Copy link
Collaborator

rustbot commented Nov 2, 2024

r? @lcnr

rustbot has assigned @lcnr.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Nov 2, 2024
@rustbot
Copy link
Collaborator

rustbot commented Nov 2, 2024

These commits modify compiler targets.
(See the Target Tier Policy.)

@ilovepi
Copy link
Contributor Author

ilovepi commented Nov 2, 2024

r? @tmandry

@rustbot rustbot assigned tmandry and unassigned lcnr Nov 2, 2024
@ilovepi
Copy link
Contributor Author

ilovepi commented Nov 2, 2024

Also, I'm open to testing this differently, but I'm not aware of any way to be sure the codegen flags are set appropriately, other than to see if LLVM spits out the right bits, since there isn't anything to check in the LLVM IR.

@rust-log-analyzer
Copy link
Collaborator

The job mingw-check-tidy failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)

COPY host-x86_64/mingw-check/validate-toolstate.sh /scripts/
COPY host-x86_64/mingw-check/validate-error-codes.sh /scripts/

# NOTE: intentionally uses python2 for x.py so we can test it still works.
# validate-toolstate only runs in our CI, so it's ok for it to only support python3.
ENV SCRIPT TIDY_PRINT_DIFF=1 python2.7 ../x.py test \
           --stage 0 src/tools/tidy tidyselftest --extra-checks=py:lint,cpp:fmt
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
#    pip-compile --allow-unsafe --generate-hashes reuse-requirements.in
---
#13 2.774 Building wheels for collected packages: reuse
#13 2.775   Building wheel for reuse (pyproject.toml): started
#13 3.017   Building wheel for reuse (pyproject.toml): finished with status 'done'
#13 3.018   Created wheel for reuse: filename=reuse-4.0.3-cp310-cp310-manylinux_2_35_x86_64.whl size=132720 sha256=026f3bb0f1aa8090b861fd0a0939cb1a782396d84c8aab7875096557d637a0f6
#13 3.018   Stored in directory: /tmp/pip-ephem-wheel-cache-zyjglt7b/wheels/3d/8d/0a/e0fc6aba4494b28a967ab5eaf951c121d9c677958714e34532
#13 3.021 Installing collected packages: boolean-py, binaryornot, tomlkit, reuse, python-debian, markupsafe, license-expression, jinja2, chardet, attrs
#13 3.414 Successfully installed attrs-23.2.0 binaryornot-0.4.4 boolean-py-4.0 chardet-5.2.0 jinja2-3.1.4 license-expression-30.3.0 markupsafe-2.1.5 python-debian-0.1.49 reuse-4.0.3 tomlkit-0.13.0
#13 3.415 WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
#13 3.935 Collecting virtualenv
#13 3.935 Collecting virtualenv
#13 3.970   Downloading virtualenv-20.27.1-py3-none-any.whl (3.1 MB)
#13 4.025      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.1/3.1 MB 59.0 MB/s eta 0:00:00
#13 4.067 Collecting distlib<1,>=0.3.7
#13 4.071   Downloading distlib-0.3.9-py2.py3-none-any.whl (468 kB)
#13 4.078      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 469.0/469.0 KB 87.6 MB/s eta 0:00:00
#13 4.113 Collecting filelock<4,>=3.12.2
#13 4.116   Downloading filelock-3.16.1-py3-none-any.whl (16 kB)
#13 4.147 Collecting platformdirs<5,>=3.9.1
#13 4.150   Downloading platformdirs-4.3.6-py3-none-any.whl (18 kB)
#13 4.232 Installing collected packages: distlib, platformdirs, filelock, virtualenv
#13 4.406 Successfully installed distlib-0.3.9 filelock-3.16.1 platformdirs-4.3.6 virtualenv-20.27.1
#13 DONE 4.5s

#14 [7/8] COPY host-x86_64/mingw-check/validate-toolstate.sh /scripts/
#14 DONE 0.0s
---
DirectMap4k:      192448 kB
DirectMap2M:     8196096 kB
DirectMap1G:    10485760 kB
##[endgroup]
Executing TIDY_PRINT_DIFF=1 python2.7 ../x.py test            --stage 0 src/tools/tidy tidyselftest --extra-checks=py:lint,cpp:fmt
+ TIDY_PRINT_DIFF=1 python2.7 ../x.py test --stage 0 src/tools/tidy tidyselftest --extra-checks=py:lint,cpp:fmt
    Finished `dev` profile [unoptimized] target(s) in 0.04s
##[endgroup]
downloading https://static.rust-lang.org/dist/2024-10-16/rustfmt-nightly-x86_64-unknown-linux-gnu.tar.xz
extracting /checkout/obj/build/cache/2024-10-16/rustfmt-nightly-x86_64-unknown-linux-gnu.tar.xz to /checkout/obj/build/x86_64-unknown-linux-gnu/rustfmt
---
##[endgroup]
fmt check
Diff in /checkout/compiler/rustc_codegen_llvm/src/back/write.rs:26:
 };
 use rustc_span::InnerSpan;
 use rustc_span::symbol::sym;
-use rustc_target::spec::{CodeModel, RelocModel, SanitizerSet, SplitDebuginfo, TlsDialect, TlsModel};
+use rustc_target::spec::{
+    CodeModel, RelocModel, SanitizerSet, SplitDebuginfo, TlsDialect, TlsModel,
 use tracing::debug;
 
 use crate::back::lto::ThinBuffer;
Diff in /checkout/compiler/rustc_codegen_llvm/src/lib.rs:312:
Diff in /checkout/compiler/rustc_codegen_llvm/src/lib.rs:312:
             }
             PrintKind::TlsDialect => {
                 writeln!(out, "Available TLS dialects:").unwrap();
-                for name in
-                    &["trad", "desc"]
-                {
+                for name in &["trad", "desc"] {
                     writeln!(out, "    {name}").unwrap();
                 writeln!(out).unwrap();
Diff in /checkout/compiler/rustc_codegen_llvm/src/lib.rs:321:
             }
-
-
 
             PrintKind::StackProtectorStrategies => {
                 writeln!(
fmt error: Running `"/checkout/obj/build/x86_64-unknown-linux-gnu/rustfmt/bin/rustfmt" "--config-path" "/checkout" "--edition" "2021" "--unstable-features" "--skip-children" "--check" "/checkout/compiler/rustc_codegen_llvm/src/asm.rs" "/checkout/compiler/rustc_data_structures/src/sorted_map/index_map.rs" "/checkout/compiler/rustc_codegen_llvm/src/builder.rs" "/checkout/compiler/rustc_data_structures/src/svh.rs" "/checkout/compiler/rustc_data_structures/src/intern.rs" "/checkout/compiler/rustc_data_structures/src/flock.rs" "/checkout/compiler/rustc_data_structures/src/binary_search_util/tests.rs" "/checkout/compiler/rustc_data_structures/src/binary_search_util/mod.rs" "/checkout/compiler/rustc_data_structures/src/stable_hasher/tests.rs" "/checkout/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs" "/checkout/compiler/rustc_data_structures/src/snapshot_map/tests.rs" "/checkout/compiler/rustc_data_structures/src/snapshot_map/mod.rs" "/checkout/compiler/rustc_data_structures/src/sharded.rs" "/checkout/compiler/rustc_data_structures/src/small_c_str.rs" "/checkout/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/cpp_like.rs" "/checkout/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/native.rs" "/checkout/compiler/rustc_codegen_llvm/src/debuginfo/metadata/enums/mod.rs" "/checkout/compiler/rustc_data_structures/src/tagged_ptr/drop.rs" "/checkout/compiler/rustc_codegen_llvm/src/debuginfo/metadata/type_map.rs" "/checkout/compiler/rustc_data_structures/src/tagged_ptr/copy.rs" "/checkout/compiler/rustc_codegen_llvm/src/debuginfo/create_scope_map.rs" "/checkout/compiler/rustc_codegen_llvm/src/debuginfo/utils.rs" "/checkout/compiler/rustc_codegen_llvm/src/debuginfo/namespace.rs" "/checkout/compiler/rustc_codegen_llvm/src/debuginfo/mod.rs" "/checkout/compiler/rustc_codegen_llvm/src/debuginfo/gdb.rs" "/checkout/compiler/rustc_data_structures/src/tagged_ptr/impl_tag/tests.rs" "/checkout/compiler/rustc_data_structures/src/tagged_ptr/impl_tag.rs" "/checkout/compiler/rustc_data_structures/src/tagged_ptr/drop/tests.rs" "/checkout/compiler/rustc_codegen_llvm/src/back/lto.rs" "/checkout/compiler/rustc_codegen_llvm/src/back/write.rs" "/checkout/compiler/rustc_codegen_llvm/src/back/profiling.rs" "/checkout/compiler/rustc_codegen_llvm/src/back/archive.rs" "/checkout/compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs" "/checkout/compiler/rustc_codegen_llvm/src/abi.rs" "/checkout/compiler/rustc_codegen_llvm/src/context.rs" "/checkout/compiler/rustc_codegen_llvm/src/llvm_util.rs" "/checkout/compiler/rustc_codegen_llvm/src/va_arg.rs" "/checkout/compiler/rustc_codegen_llvm/src/intrinsic.rs" "/checkout/compiler/rustc_codegen_llvm/src/declare.rs" "/checkout/compiler/rustc_codegen_llvm/src/allocator.rs" "/checkout/compiler/rustc_codegen_llvm/src/type_.rs" "/checkout/compiler/rustc_codegen_llvm/src/errors.rs" "/checkout/compiler/rustc_codegen_llvm/src/attributes.rs" "/checkout/compiler/rustc_codegen_llvm/src/consts.rs" "/checkout/compiler/rustc_codegen_llvm/src/type_of.rs" "/checkout/compiler/rustc_codegen_llvm/src/lib.rs" "/checkout/compiler/rustc_codegen_llvm/src/mono_item.rs" "/checkout/compiler/rustc_data_structures/src/tagged_ptr/copy/tests.rs" "/checkout/compiler/rustc_data_structures/src/temp_dir.rs" "/checkout/compiler/rustc_codegen_llvm/src/coverageinfo/map_data.rs" "/checkout/compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs" "/checkout/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs" "/checkout/compiler/rustc_codegen_llvm/src/coverageinfo/mod.rs" "/checkout/compiler/rustc_codegen_llvm/src/common.rs" "/checkout/compiler/rustc_codegen_llvm/src/base.rs" "/checkout/compiler/rustc_data_structures/src/graph/reference.rs" "/checkout/compiler/rustc_codegen_llvm/src/llvm/diagnostic.rs" "/checkout/compiler/rustc_codegen_llvm/src/llvm/ffi.rs" "/checkout/compiler/rustc_codegen_llvm/src/llvm/archive_ro.rs" "/checkout/compiler/rustc_codegen_llvm/src/llvm/mod.rs" "/checkout/compiler/rustc_codegen_llvm/src/callee.rs" "/checkout/compiler/rustc_codegen_llvm/src/value.rs" "/checkout/compiler/rustc_data_structures/src/graph/iterate/tests.rs" "/checkout/compiler/rustc_data_structures/src/sorted_map/tests.rs"` failed.
If you're running `tidy`, try again with `--bless`. Or, if you just want to format code, run `./x.py fmt` instead.
  local time: Sat Nov  2 00:17:50 UTC 2024
  network time: Sat, 02 Nov 2024 00:17:50 GMT
##[error]Process completed with exit code 1.
Post job cleanup.

@@ -2114,6 +2123,9 @@ written to standard error output)"),
#[rustc_lint_opt_deny_field_access("use `Session::tls_model` instead of this field")]
tls_model: Option<TlsModel> = (None, parse_tls_model, [TRACKED],
"choose the TLS model to use (`rustc --print tls-models` for details)"),
#[rustc_lint_opt_deny_field_access("use `Session::tls_model` instead of this field")]
Copy link
Member

Choose a reason for hiding this comment

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

*tls_model

@@ -825,6 +825,7 @@ pub enum PrintKind {
RelocationModels,
CodeModels,
TlsModels,
TlsDialect,
Copy link
Member

Choose a reason for hiding this comment

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

Why is this added? Do you have any actual need to know which tls dialect will ne used by rustc on a given target?

Copy link
Member

Choose a reason for hiding this comment

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

Also it should probably be made available on nightly only so we can remove it again once the traditional tls dialect is no longer used anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why is this added? Do you have any actual need to know which tls dialect will ne used by rustc on a given target?

I followed the example for TlsModels, since they seem of equivalent diagnostic value to me. For Android RISC-V targets TLSDESC is the default, and I think it may be a requirement.

Also it should probably be made available on nightly only so we can remove it again once the traditional tls dialect is no longer used anywhere.

I'm not sure that will ever happen. TLSDESC requires support in the dynamic linker, so its imaginable that not all libcs will support that. For instance I've seen embedded targets that implement something like a loader service that support __tls_get_addr with overlays, but do not support TLSDESC.

At any rate, I'm not sure we will ever drop support for traditional TLS in LLVM. It's been discussed, but I think its unlikely until GNU toolchains drop support.

Copy link
Member

@bjorn3 bjorn3 Nov 6, 2024

Choose a reason for hiding this comment

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

I'm not sure that will ever happen. TLSDESC requires support in the dynamic linker, so its imaginable that not all libcs will support that.

It can be the default on targets that do support it and not having an option to enable it on targets that don't support it.

At any rate, I'm not sure we will ever drop support for traditional TLS in LLVM. It's been discussed, but I think its unlikely until GNU toolchains drop support.

I mean rustc dropping support for configuring it to anything other than the default for the target.

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'm not sure that will ever happen. TLSDESC requires support in the dynamic linker, so its imaginable that not all libcs will support that.

It can be the default on targets that do support it and not having an option to enable it on targets that don't support it.

I'm not sure how you can make that determination, since you can't know which libc someone is using ahead of time. Being able to configure and opt in/out is necessary, IMO.

At any rate, I'm not sure we will ever drop support for traditional TLS in LLVM. It's been discussed, but I think its unlikely until GNU toolchains drop support.

I mean rustc dropping support for configuring it to anything other than the default for the target.

Failing to expose a code generation option doesn't sound right to me. There are valid reasons someone may want to select a particular variant of an option differently than the default. Until LLVM (and I guess all backends?) drops support altogether, I'm not sure its a good idea for rustc to stop exposing that option altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If there is a chance that LLVM ever drops the option, then no, we should not expose the option.

AFAIK, LLVM has no plans to drop support. Its hard to say never, but as a mater of practicality, I doubt it will be removed any time soon. If it does, we'd probably have a long period where its listed as "deprecated". Even then its possible it wouldn't actually be removed. There is precedent for that.

There will be options to pass arguments directly to codegen backends, and if those suffice, then great, but otherwise we should resist getting into the habit of gaslighting our users by tricking them into thinking they have power and then not being able to fulfill their requests later because of the whims of those codegen backends.

In general, passing -mllvm options often fails for LTO builds, because frontends tend to fail to pass those along correctly to the linker via -Wl,-mllvm,-option. This was particularly painful for RISC-V targets whose target features were not propagated to the linker correctly, and required extra build plumbing to solve. It's arguably a failing of LLVM for not encoding those options in the IR. How to solve this class of problems has been being discussed for a long time now, and I don't think we'll arrive at a solution any time soon.

It would be good to spell those out.

First, this comment was about compiler options in general. Defaults are usually chosen to make sense for the most common software. Projects may want to leverage knowledge they have about their system to improve performance, security, reduce size, etc. It's quite hard to guess what requirements people may have, and failing to expose options that allows them to deviate from the default feels like the wrong approach for a compiler.

For TLS dialects, I'd say performance is the most obvious motivation. Another thing to consider is that you may want to support different configurations for the same target triple. For instance, Android after some particular SDK version may want to drop TLSDESC support, but you may need to retain the ability to target earlier SDKs that predate TLSDESC support in bionic. Similarly, we know that Fuchsia's driver ABI will only support TLSDESC, but the rest of the system will likely be more flexible about supporting both TLSDESC and traditional TLS.

Copy link
Member

Choose a reason for hiding this comment

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

For instance, Android after some particular SDK version may want to drop TLSDESC support, but you may need to retain the ability to target earlier SDKs that predate TLSDESC support in bionic.

Is Android planning to drop support for not using TLSDESC before the last LTS NDK that doesn't support TLSDESC is no longer supported? We only support the most recent LTS NDK.

Similarly, we know that Fuchsia's driver ABI will only support TLSDESC, but the rest of the system will likely be more flexible about supporting both TLSDESC and traditional TLS.

If Fuchsia has a different ABI between regular userspace and drivers, it would make sense to have separate targets, right? Does anyone care about supporting Fuchsia versions from before TLSDESC got introduced in the first place?

Copy link
Member

Choose a reason for hiding this comment

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

It's quite hard to guess what requirements people may have, and failing to expose options that allows them to deviate from the default feels like the wrong approach for a compiler.

This argument generalizes too much. It could mean that every single decision the compiler could make should have a user-facing option.

Compiler flags are part of our user interface. Which means they need testing, we need to consider compatibility, ABI unsoundness, terrible linker errors and so on. So adding one should come with a stronger motivation than "it'd be nice to have a knob, even though there's no immediate need".

As bjorn says it's possible that this decision is better left to a combination of target specifications and the compiler checking if it's supported (e.g. based on LLVM version) and determining the correct setting based on that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For instance, Android after some particular SDK version may want to drop TLSDESC support, but you may need to retain the ability to target earlier SDKs that predate TLSDESC support in bionic.

Is Android planning to drop support for not using TLSDESC before the last LTS NDK that doesn't support TLSDESC is no longer supported? We only support the most recent LTS NDK.

I doubt it, but I'm not an Android maintainer and the precise details are outside of my area of expertise. I mentioned this, since similar concerns were brought up on the LLVM side w.r.t. emulated TLS and the NDK's need to maintain support for it until the NDK versions that used it were no longer supported. Those time horizons were typically long, IIRC. If Rust has a support story here tied to the NDK then maybe the concern I raised is moot.

Similarly, we know that Fuchsia's driver ABI will only support TLSDESC, but the rest of the system will likely be more flexible about supporting both TLSDESC and traditional TLS.

If Fuchsia has a different ABI between regular userspace and drivers, it would make sense to have separate targets, right?

Drivers in Fuchsia are still user-level programs, and I'm fairly sure we don't want to introduce another triple for them.

Does anyone care about supporting Fuchsia versions from before TLSDESC got introduced in the first place?

Fuchsia was brought up as an example of a scenario where its useful to have such knobs. I don't think the focus should be on Fuchsia specifically. FWIW, though, we do certainly support older versions of our SDK, though those are usually tied to a specific toolchain and set of libraries.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's quite hard to guess what requirements people may have, and failing to expose options that allows them to deviate from the default feels like the wrong approach for a compiler.

This argument generalizes too much. It could mean that every single decision the compiler could make should have a user-facing option.

There's an argument that it should, even if its not easy to access. Not all software is created equal, and not all compiler decisions are appropriate everywhere. I'm not suggesting that be made the case, just that in many cases not leaving a escape hatch for an optimization leads to worse performance, code size, or a security problem that has to be worked around.

Compiler flags are part of our user interface. Which means they need testing, we need to consider compatibility, ABI unsoundness, terrible linker errors and so on. So adding one should come with a stronger motivation than "it'd be nice to have a knob, even though there's no immediate need".

I don't view this as "nice to have," I view it as a requirement. Given the history of these options, it seems irrational to fail to give developers the choice of what to do if their needs differ from the default. I'm fine to leave the disagreement there, but I'm not doing this work because projects I support don't need it. Clearly the fact that I'm implementing it suggests some level of need.

As bjorn says it's possible that this decision is better left to a combination of target specifications and the compiler checking if it's supported (e.g. based on LLVM version) and determining the correct setting based on that.

I disagree. IMO, setting defaults based on the triple is sensible, but locking users in is not, especially when the backend supports both.

@@ -2114,6 +2123,9 @@ written to standard error output)"),
#[rustc_lint_opt_deny_field_access("use `Session::tls_model` instead of this field")]
tls_model: Option<TlsModel> = (None, parse_tls_model, [TRACKED],
"choose the TLS model to use (`rustc --print tls-models` for details)"),
#[rustc_lint_opt_deny_field_access("use `Session::tls_model` instead of this field")]
tls_dialect: Option<TlsDialect> = (None, parse_tls_dialect, [TRACKED],
Copy link
Member

Choose a reason for hiding this comment

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

Maybe name this -Zuse-tlsdesc and have it take a bool? I don't suppose we get another dialect before the transition from traditional to tlsdesc TLS support is finished, right?

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'm OK w/ that, but I think its usually better to support an existing spelling when there are existing conventions in other compilers. We'd have to check w/ folks doing the GCC backend work, but its possible the variants would be easier to integrate for them.

We've talked about adding support for a 3 GOT slot variant of TLSDESC. That would require support from the linker and libc. There are advantages to doing it that way, like avoiding a level of indirection to read the descriptor for the dynamic cases, but I'm not sure how likely that is to ever happen. I'd like to see it, and was hoping that would be the ABI for RISC-V, but it would be a win for any architecture.

But I can change this to a boolean if you'd like.

@bjorn3
Copy link
Member

bjorn3 commented Nov 2, 2024

In the future, if LLVM changes its defaults for TLS to enable TLSDESC for a particular platform or architecture, users who do not wish to change can use the new option to select their desired TLS ABI.

Not quite. You did still have to deal with a precompiled standard library that uses TLSDESC in that case. I think we did have to keep TLSDESC disabled by default until the minimum supported libc and linker for the target support TLSDESC, at which point nobody needs to disable TLSDESC usage.

@deltragon
Copy link
Contributor

since it is an important part of a program's ABI.

Does that mean that all compilation units in a binary need to use the same TLS dialect, or otherwise it would be unsound?

If that is the case, this is should use the target modifier mechanism of rust-lang/rfcs#3716 once that exists. In the meantime, that should at least be documented clearly (as it may also need -Zbuild-std to use correctly, I presume).

@bors
Copy link
Contributor

bors commented Nov 3, 2024

☔ The latest upstream changes (presumably #125579) made this pull request unmergeable. Please resolve the merge conflicts.

@ilovepi
Copy link
Contributor Author

ilovepi commented Nov 6, 2024

In the future, if LLVM changes its defaults for TLS to enable TLSDESC for a particular platform or architecture, users who do not wish to change can use the new option to select their desired TLS ABI.

Not quite. You did still have to deal with a precompiled standard library that uses TLSDESC in that case. I think we did have to keep TLSDESC disabled by default until the minimum supported libc and linker for the target support TLSDESC, at which point nobody needs to disable TLSDESC usage.

Today, if the standard library is compiled w/ whatever LLVM says is the default, and you have no mechanism to opt out of it. Users should be able to choose, even if they need to compile their own standard library. There also isn't a problem mixing traditional TLS and TLSDESC so long as the target libc supports both. If it only supports one, then you're kind of out of luck. That's one reason I don't expect this option to go away: you may need to target a system that only supports one specific flavor of TLS.

For example, Fuchsia's driver ABI is only expected to work w/ TLSDESC, and they won't have access to __tls_get_addr(). The reasons for this are fairly technical, and deeply rooted in the vagaries of its dynamic linker and other low level system guarantees.

@ilovepi
Copy link
Contributor Author

ilovepi commented Nov 6, 2024

since it is an important part of a program's ABI.

Does that mean that all compilation units in a binary need to use the same TLS dialect, or otherwise it would be unsound?

In general, no. Most libc implementations support both. That isn't universally true, but they aren't incompatible to use together. Whether both are supported by the libc is a different question.

If that is the case, this is should use the target modifier mechanism of rust-lang/rfcs#3716 once that exists. In the meantime, that should at least be documented clearly (as it may also need -Zbuild-std to use correctly, I presume).

I don't think that's required here. This is a different issue than something like mixing RISC-V atomics ABIs or Shadow Call Stack implementations, where you're mixing objects that are fundamentally incompatible/broken.

The dynamic linker should issue an error about an unsupported relocation type when loading a module if it doesn't support TLSDESC. Similarly, you should get some kind of linker error if you use traditional TLS and there isn't an implementation of __tls_get_addr() available.

@bjorn3
Copy link
Member

bjorn3 commented Nov 6, 2024

Today, if the standard library is compiled w/ whatever LLVM says is the default, and you have no mechanism to opt out of it. Users should be able to choose, even if they need to compile their own standard library.

We would only make TLSDESC the default once we bump the minimal libc version we need to one that always supports TLSDESC. At that point even disabling TLSDESC would not create a working executable for the target anymore as we did also start to depend on api's that older libc versions don't support.

@workingjubilee
Copy link
Member

The dynamic linker should

should. does it always? is this hypothesis or verified experiment?

@workingjubilee
Copy link
Member

Today, if the standard library is compiled w/ whatever LLVM says is the default, and you have no mechanism to opt out of it. Users should be able to choose, even if they need to compile their own standard library. There also isn't a problem mixing traditional TLS and TLSDESC so long as the target libc supports both. If it only supports one, then you're kind of out of luck. That's one reason I don't expect this option to go away: you may need to target a system that only supports one specific flavor of TLS.

If you need to target a system that only supports one specific flavor of TLS, then all adding a flag to the CLI does is expose a way to get things wrong. If you need to generate a code for such a unique target tuple, then you probably should be generating code for a unique target tuple, instead of trying to pretend some other target adequately describes what you need.

Copy link
Member

@workingjubilee workingjubilee left a comment

Choose a reason for hiding this comment

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

We support non-ELF targets. This option only affects ELF targets. Not all the world is Linux. Please add a UI test that verifies this compiler option is rejected and compilation ceases if you try to use it for a non-ELF target. This will become more important if any other object formats adopt a similar "dialect" but don't implement it exactly the same.

@ilovepi
Copy link
Contributor Author

ilovepi commented Nov 6, 2024

The dynamic linker should

should. does it always? is this hypothesis or verified experiment?

Bionic, musl, and glibc do. I can't speak to every implementation, though.

@ilovepi
Copy link
Contributor Author

ilovepi commented Nov 6, 2024

If you need to target a system that only supports one specific flavor of TLS, then all adding a flag to the CLI does is expose a way to get things wrong. If you need to generate a code for such a unique target tuple, then you probably should be generating code for a unique target tuple, instead of trying to pretend some other target adequately describes what you need.

Targets often support multiple variations, and users should be able to pick which one they use. Adding an extra tuple for every combination of potential features doesn't sound right, but maybe I'm misunderstanding what you're saying?

For targets that can't/won't support this at all (e.g. non-ELF targets), an error from the frontend sounds like a reasonable approach.

We support non-ELF targets. This option only affects ELF targets. Not all the world is Linux. Please add a UI test that verifies this compiler option is rejected and compilation ceases if you try to use it for a non-ELF target. This will become more important if any other object formats adopt a similar "dialect" but don't implement it exactly the same.

Thanks for reminding me, I had meant to add something along those lines for COFF and MachO, and forgot.

@workingjubilee
Copy link
Member

workingjubilee commented Nov 6, 2024

Targets often support multiple variations, and users should be able to pick which one they use. Adding an extra tuple for every combination of potential features doesn't sound right, but maybe I'm misunderstanding what you're saying?

I agree with that as well. Perhaps I am just feeling skeptical as to what model you are thinking of, here, of a "target that only supports a specific TLS dialect", that it requires such configuration? I think it's very important to actually understand the model of the actual usage, instead of exposing compiler flags "just in case", because some of rustc's compiler options have been exposed but then later proved to behave in ways that break, harshly, with the model of some actual users of the compiler.

For instance, if a target doesn't support a TLS dialect, but is ELF, and we know it doesn't support it, should we error on the clash? Prefer the target's value and silently ignore the user's request? Pass it through and just say "que sera, que sera", even though the codegen may then be invalid? What will people actually want? Ideally, we do not guess as to which people will later expect.

Maybe the primary importance of this is only for our internal documentation so we know what model we're trying to uphold.

@workingjubilee
Copy link
Member

workingjubilee commented Nov 7, 2024

For instance, if a target doesn't support a TLS dialect, but is ELF, and we know it doesn't support it, should we error on the clash? Prefer the target's value and silently ignore the user's request? Pass it through and just say "que sera, que sera", even though the codegen may then be invalid?

Also, to be clear, we don't have to answer this right now, I'm just pointing out that this is a question that can in fact be answered different ways, so we should try to actually make note of an unresolved question.

@ilovepi
Copy link
Contributor Author

ilovepi commented Nov 7, 2024

Targets often support multiple variations, and users should be able to pick which one they use. Adding an extra tuple for every combination of potential features doesn't sound right, but maybe I'm misunderstanding what you're saying?

I agree with that as well. Perhaps I am just feeling skeptical as to what model you are thinking of, here, of a "target that only supports a specific TLS dialect", that it requires such configuration?

There are going to be subsets of Fuchsia that will have different requirements than we're currently planning to impose on other parts of the system. As mentioned earlier, Fuchsia drivers will be required to use TLSDESC, and those won't have a different target triple. Right now, the plan of record is that other components probably should use TLSDESC too, but won't be required (though that is still TBD).

My understanding of Rust support in Android and its relation it the NDK is very incomplete, but I think they'll need to support both options depending on the SDK being targeted. Do take that with a grain of salt, as I could easily be incorrect about how those things intersect for Android specifically.

Lastly, I've seen embedded systems that don't have a libc, or at least don't use one with TLS support, but have implemented TLS support themselves. Those systems need to be able to select their code generation but the target triple used is typically something a bit generic for baremetal systems. Since they're providing support outside of a libc(or on in addition to one), the triple also isn't informative about the code generation requirements.

I think it's very important to actually understand the model of the actual usage, instead of exposing compiler flags "just in case", because some of rustc's compiler options have been exposed but then later proved to behave in ways that break, harshly, with the model of some actual users of the compiler.

I don't know that there's a whole lot you can do about that problem, at least in the general case. Compilers often expose powerful options to users, and sometimes those conflict. I've seen this happen the most frequently with security related options, but there are certainly code generation options that may violate some invariants that people rely upon. Its hard to know how to balance those exactly, since there are bound to be users that need/would greatly benefit from those options, but using them correctly requires a certain degree of familiarity with the low level details of the program and its invariants. Other that better documentation and outreach I'm not sure we can do all that much to satisfy the expert user and not introduce a potential foot gun for other users.

For instance, if a target doesn't support a TLS dialect, but is ELF, and we know it doesn't support it, should we error on the clash?

I'm far from an authority on how compiler UX, but if we know for sure, then I'd say "yes, we should emit an error." If LLVM knows its incompatible, its going to error anyway, just much later. It's always better to issue such an error in the frontend if you can, and I'd expect the frontend to behave the same way as it does for non-ELF targets. "Knows" is doing some heavy lifting here though.

@bjorn3
Copy link
Member

bjorn3 commented Nov 7, 2024

There are going to be subsets of Fuchsia that will have different requirements than we're currently planning to impose on other parts of the system. As mentioned earlier, Fuchsia drivers will be required to use TLSDESC, and those won't have a different target triple. Right now, the plan of record is that other components probably should use TLSDESC too, but won't be required (though that is still TBD).

Using TLSDESC for the entire system should be fine, right? I presume there are no components that must not use TLSDESC and at least as far as I can tell as outsider, there is no upstream support for Fuchsia versions older than the latest one and nobody outside of Google depends on using old Fuchsia versions either, so having the first version of Fuchsia that supports TLSDESC as minimum supported version on the rust side should be fine, thus enabling the fuchsia rust target to always enable TLSDESC.

Lastly, I've seen embedded systems that don't have a libc, or at least don't use one with TLS support, but have implemented TLS support themselves. Those systems need to be able to select their code generation but the target triple used is typically something a bit generic for baremetal systems. Since they're providing support outside of a libc(or on in addition to one), the triple also isn't informative about the code generation requirements.

We don't have stable support for TLS on any baremetal targets at all. All stable TLS usage goes through the thread_local! macro in libstd which is inaccessible on baremetal targets. TLS is part of the ABI with libc (or equivalent). If this ABI is different from what is specified in the target tuple you want to use, right now you have to write your own target spec json which specifies which ABI to use anyway.

@ilovepi
Copy link
Contributor Author

ilovepi commented Nov 7, 2024

There are going to be subsets of Fuchsia that will have different requirements than we're currently planning to impose on other parts of the system. As mentioned earlier, Fuchsia drivers will be required to use TLSDESC, and those won't have a different target triple. Right now, the plan of record is that other components probably should use TLSDESC too, but won't be required (though that is still TBD).

Using TLSDESC for the entire system should be fine, right? I presume there are no components that must not use TLSDESC and at least as far as I can tell as outsider, there is no upstream support for Fuchsia versions older than the latest one and nobody outside of Google depends on using old Fuchsia versions either, so having the first version of Fuchsia that supports TLSDESC as minimum supported version on the rust side should be fine, thus enabling the fuchsia rust target to always enable TLSDESC.

I don't think that is for a toolchain to decide. We're not prepared to make that determination yet for our entire platform and the ABIs we support. If we do, then sure, maybe that's fine for Fuchsia. But Fuchsia isn't the only platform that can use the option.

We don't have stable support for TLS on any baremetal targets at all. All stable TLS usage goes through the thread_local! macro in libstd which is inaccessible on baremetal targets. TLS is part of the ABI with libc (or equivalent). If this ABI is different from what is specified in the target tuple you want to use, right now you have to write your own target spec json which specifies which ABI to use anyway.

I'm not familiar with target spec json mechanism, so I'll have to look at that. What I'm saying is that there are real needs in this space to be able to generate code this way for real products.

@workingjubilee
Copy link
Member

I'm not familiar with target spec json mechanism, so I'll have to look at that. What I'm saying is that there are real needs in this space to be able to generate code this way for real products.

The target-spec.json is a mechanism that effectively allows setting every parameter on TargetOptions, especially including the ones which explode your compiled artifact if you set them incorrectly and which we would prefer not to expose as flags. While it is unstable... it is tied to some internal details... in practice it is an available mechanism and some communities rely heavily on it. We have occasionally discussed phasing it out for an alternative, but it's not going to evaporate tomorrow, especially as it comes up quite a lot for OS dev.

I think it's very important to actually understand the model of the actual usage, instead of exposing compiler flags "just in case", because some of rustc's compiler options have been exposed but then later proved to behave in ways that break, harshly, with the model of some actual users of the compiler.

I don't know that there's a whole lot you can do about that problem, at least in the general case. Compilers often expose powerful options to users, and sometimes those conflict.

The reason I stated all those as options we may select between is because without documenting such, a reviewer can wind up approving a PR that subtly changes the behavior in ways users don't expect and was insufficiently tested. It may change to directly contradict its previous behavior in some cases. Then, if not many people are using the combination of circumstances that changed behavior, and they aren't inspecting the assembly very closely, it may be that no one reports a regression for years.

Then, when someone finally does report it, this can make everyone unhappy if we even discuss a revert of what was thought to be a stable behavior and has become relied on for some time. This has happened to rustc for poorly-documented compiler options before, and you bet it has happened to the C compilers!

The best way to prevent this is often to actually write down the spec instead of "I dunno, whatever gcc or clang does I guess?" so that there is an intention to extrapolate from when later combinations come up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. 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.

10 participants