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

Emit error when calling/declaring functions with vectors that require missing target feature #127731

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

Conversation

veluca93
Copy link
Contributor

@veluca93 veluca93 commented Jul 14, 2024

On some architectures, vector types may have a different ABI depending on whether the relevant target features are enabled. (The ABI when the feature is disabled is often not specified, but LLVM implements some de-facto ABI.)

As discussed in rust-lang/lang-team#235, this turns out to very easily lead to unsound code.

This commit makes it a post-monomorphization error to declare or call functions using those vector types in a context in which the corresponding target features are disabled, if using an ABI for which the difference is relevant. This ensures that these functions are always called with a consistent ABI.

See the nomination comment for more discussion.

r? RalfJung

Fixes #116558

@rustbot
Copy link
Collaborator

rustbot commented Jul 14, 2024

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @RalfJung (or someone else) some time within the next two weeks.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue

@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 Jul 14, 2024
@rustbot
Copy link
Collaborator

rustbot commented Jul 14, 2024

These commits modify the Cargo.lock file. Unintentional changes to Cargo.lock can be introduced when switching branches and rebasing PRs.

If this was unintentional then you should revert the changes before this PR is merged.
Otherwise, you can ignore this comment.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

Copy link
Member

@RalfJung RalfJung left a comment

Choose a reason for hiding this comment

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

Please also add some tests so that we can see this check in action.

compiler/rustc_monomorphize/Cargo.toml Show resolved Hide resolved
compiler/rustc_monomorphize/src/collector.rs Outdated Show resolved Hide resolved
compiler/rustc_monomorphize/src/collector/abi_check.rs Outdated Show resolved Hide resolved
compiler/rustc_monomorphize/src/collector/abi_check.rs Outdated Show resolved Hide resolved
compiler/rustc_monomorphize/src/collector/abi_check.rs Outdated Show resolved Hide resolved
compiler/rustc_monomorphize/src/collector/abi_check.rs Outdated Show resolved Hide resolved
compiler/rustc_monomorphize/src/collector/abi_check.rs Outdated Show resolved Hide resolved
compiler/rustc_monomorphize/src/collector/abi_check.rs Outdated Show resolved Hide resolved
compiler/rustc_monomorphize/src/collector/abi_check.rs Outdated Show resolved Hide resolved
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@veluca93 veluca93 force-pushed the abi_checks branch 2 times, most recently from ef2609c to e8302b3 Compare July 28, 2024 20:36
@RalfJung
Copy link
Member

RalfJung commented Aug 1, 2024

Looks good for the initial draft, let's see what crater says. :)

@bors try

bors added a commit to rust-lang-ci/rust that referenced this pull request Aug 1, 2024
Emit error when calling/declaring functions with unavailable vectors.

On some architectures, vector types may have a different ABI when relevant target features are enabled.

As discussed in rust-lang/lang-team#235, this turns out to very easily lead to unsound code.

This commit makes it an error to declare or call functions using those vector types in a context in which the corresponding target features are disabled, if using an ABI for which the difference is relevant.

r? RalfJung
@bors
Copy link
Contributor

bors commented Aug 1, 2024

⌛ Trying commit e8302b3 with merge 7587ff3...

@bors
Copy link
Contributor

bors commented Aug 1, 2024

☀️ Try build successful - checks-actions
Build commit: 7587ff3 (7587ff3622fbec0abf6ac551eab5226f22f5d958)

@RalfJung
Copy link
Member

RalfJung commented Aug 1, 2024

@craterbot check

@craterbot
Copy link
Collaborator

👌 Experiment pr-127731 created and queued.
🤖 Automatically detected try build 7587ff3
🔍 You can check out the queue and this experiment's details.

ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@craterbot
Copy link
Collaborator

🚧 Experiment pr-127731 is now running

ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@craterbot
Copy link
Collaborator

🎉 Experiment pr-127731 is completed!
📊 144 regressed and 10 fixed (488045 total)
📰 Open the full report.

⚠️ If you notice any spurious failure please add them to the blacklist!
ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@craterbot craterbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-crater Status: Waiting on a crater run to be completed. labels Aug 13, 2024
@RalfJung
Copy link
Member

RalfJung commented Aug 14, 2024

There's only 2 crates.io failures and they are both spurious. And the github failures I checked were also all spurious. So looking pretty good I would say. :)

Seems like the next step process-wise is to check back in with the lang team, now that we have crater results. (That's based on this message.)
@rust-lang/lang this aims to fix #116558, which is part of the discussion we had in rust-lang/lang-team#235. The way it is fixed is by making it a hard error to call or declare an extern "C" function that passes a SIMD type by-value without enabling the target feature that enables SIMD vectors of the appropriate size. The reason it is made a hard error is that these functions end up having a different ABI depending on whether that target feature is enabled or not. "Rust" ABI functions are not affected as those functions pass SIMD vectors indirectly (they do that precisely to avoid these ABI issues).

There are two points we'd like your take on:

  • How to stage this. Should it become a hard error immediately, or first be reported as a future-compat lint (I would say we can make it show up in dependencies immediately)? Crater found no regressions (probably because extern "C" functions are generally fairly rare), but of course crater doesn't see all the codebases out there.
  • What exactly should be checked? Right now, the check is extremely low-level and looks at the computed effective ABI of the function, ensuring it doesn't end up with a too large by-value SIMD vector argument. The check is done during monomorphization, each time a function is instantiated, to make sure that we can even know the actual effective ABI. More mono-time checks are unfortunate, but there's no good way to test this in generic code since we can't know whether a generic argument is a SIMD vector or not. Give that it should be rather rare to run into this, hopefully a mono-time check is acceptable.
    The other concern is semver compatibility: if a library changes a public type, e.g., from being an array of i32 to being a SIMD vector instead, that can now break downstream code. However, it can only break downstream code that passes types of this library by-value across an extern "C". If that is an FFI boundary, this is already extremely sketchy to do due to all the concerns around FFI safety and ABI compatibility. The most realistic scenario I could come up with is code using extern "C" for Rust-to-Rust calls; not sure how much we worry about such code -- I presume it is fairly rare. Still, we could try to mitigate this by making the check some sort of overapproxomation that generally refuses to pass types from other crates across an extern "C" boundary if those types could in the future be changed such that the ABI ends up with a by-value SIMD vector (i.e., (i32, ForeignType) would be okay but TransparentWrapper<ForeignType> would not). This seems quite hard to do right. I personally am not convinced that is worth the effort.

@traviscross
Copy link
Contributor

@rustbot labels +T-lang +I-lang-nominated

Nominating per the message from @RalfJung above.

@rustbot rustbot added I-lang-nominated Nominated for discussion during a lang team meeting. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Aug 14, 2024
On some architectures, vector types may have a different ABI when
relevant target features are enabled.

As discussed in rust-lang/lang-team#235, this
turns out to very easily lead to unsound code.

This commit makes it an error to declare or call functions using those
vector types in a context in which the corresponding target features are
disabled, if using an ABI for which the difference is relevant.
Comment on lines +40 to +41
if !tcx.sess.unstable_target_features.contains(&feature_sym)
&& !codegen_attrs.target_features.iter().any(|x| x.name == feature_sym)
Copy link
Member

Choose a reason for hiding this comment

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

unstable_target_features is an odd name... just leaving a note here so that the t-compiler reviewer can carefully check that this is checking the right thing: we have to ensure that the target feature is enabled when generating code for this function.

@RalfJung RalfJung added the S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). label Aug 28, 2024
@RalfJung
Copy link
Member

I helped with the initial implementation but for finalizing this, I'll hand off to someone in the compiler team.
(Also this is waiting for the lang team before it can land.)

r? compiler

@rustbot rustbot assigned pnkfelix and unassigned RalfJung Aug 28, 2024
@RalfJung RalfJung changed the title Emit error when calling/declaring functions with unavailable vectors. Emit error when calling/declaring functions with vectors that require missing target feature Sep 2, 2024
@RalfJung RalfJung added the A-target-feature Area: Enabling/disabling target features like AVX, Neon, etc. label Sep 2, 2024
@traviscross
Copy link
Contributor

We talked about this this the lang-docs call today. It'd be good to look into how this change might be documented in the Reference. It has some bearing on:

cc @RalfJung @veluca93 @chorman0773

@RalfJung
Copy link
Member

RalfJung commented Sep 4, 2024

One impact it has on the reference/docs is that we can entirely remove this section -- one can no longer cause ABI incompatibility UB using target features. That's a great win in my book. :)

@veluca93
Copy link
Contributor Author

veluca93 commented Sep 9, 2024

We talked about this this the lang-docs call today. It'd be good to look into how this change might be documented in the Reference. It has some bearing on:

cc @RalfJung @veluca93 @chorman0773

Sorry for the delay!

To my understanding, the reference changes for this PR should be minimal. I suspect a paragraph along these lines should be sufficient to describe the behaviour:

Note that, with some ABIs, the specific ISA that a function targets affects the ABI for passing certain types as arguments (or return values) of functions.
For example, on x86_64 with the "C" ABI, enabling avx makes an argument of type __m256 be passed by register instead of by stack.
This behaviour can easily lead to surprises. As such, Rust disallows

  • defining functions with arguments/return values that would change ABI if more features were enabled
  • calling functions for which some arguments would change ABI if called in a function with more features enabled

Note that neither of these situations can arise when dealing with functions that use the Rust ABI.
This behaviour is similar - but not identical - to the one of the -Wpsabi flag in some C++ compilers.

@RalfJung
Copy link
Member

RalfJung commented Sep 9, 2024

IIRC the ABI for __m256 without AVX isn't even really documented, it's just what GCC/clang happen to do? Basically that type is supposed to only exist when AVX is available, but in Rust for better or worse the type always exists, so now we have to deal with this edge case.

@chorman0773
Copy link
Contributor

chorman0773 commented Sep 9, 2024

It actually is, but how it is is subtle (and somewhat confusing).

From confirmation on the x86_64-psabi mailing list:

  • The first SSE eightbyte is passed in the next available xmm parameter register
  • The remaining SSEUP eightbytes are passed in the next available portion of that register
  • If any register used for passing a parameter is unavailable, the entire value is passed on the stack.

The TL;DR is that the psabi is supposed to treat failing to pass an SSEUP eightbyte in part of an xmm register (which naturally includes the containing ymm and zmm registers) the same as it would treat failing to pass any other class of eightbyte in a register - pass the whole value on the stack. Another way to think about it is that each eightbyte of an {x,y,z}mm register is a "separate" register that is used to pass SSEUP eightbytes. When passing __m128, __m256, or __m512 (as the first parameter/return), it's passed in {xmm0[0..64], xmm0[64..128], ymm0[128..192], ymm0[192..256], ...} and if any of those "registers" don't exist or are unavailable (ie. avx or avx512f are disabled), then we stop trying to pass the value in registers and pass the whole thing on the stack or return it in memory.

clang's behaviour is to decay the SSEUP eightbytes that can't be passed into SSE (thus passing in memory and returning in xmm0:xmm1 for __m256 values), which is incorrect (there is an llvm bug open to this effect, and its acknowledged as a bug). SSEUP eightbytes are replaced with SSE eightbytes when they aren't immediately preceeded by SSE or SSEUP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-target-feature Area: Enabling/disabling target features like AVX, Neon, etc. I-lang-nominated Nominated for discussion during a lang team meeting. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The extern "C" ABI of vector types depends on target features
10 participants