-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Reject unsupported extern "{abi}"
s consistently in all positions
#142134
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
base: master
Are you sure you want to change the base?
Reject unsupported extern "{abi}"
s consistently in all positions
#142134
Conversation
HIR ty lowering was modified cc @fmease This PR changes a file inside These commits modify Please ensure that if you've changed the output:
|
@bors2 try |
Reject `extern "{abi}"` when the target does not support it ## What Promote [`unsupported_fn_ptr_calling_conventions`] from a warning to a hard error, making sure edge-cases will not escape. We now emit hard errors for every case we would return `Invalid` from `AbiMap::canonize_abi` during AST to HIR lowering. In particular, these architecture-specific ABIs now only compile on their architectures[^1]: - amdgpu: "gpu-kernel" - arm: "aapcs", "C-cmse-nonsecure-entry" - avr: "avr-interrupt", "avr-non-blocking-interrupt" - msp430: "msp430-interrupt" - nvptx64: "gpu-kernel", "ptx-kernel" - riscv32 and riscv64: "riscv-interrupt-machine", "riscv-interrupt-supervisor" - x86: "thiscall" - x86 and x86_64: "x86-interrupt" - x86_64: "sysv64", "win64" The panoply of ABIs that are logically x86-specific but actually permitted on all Windows targets remain supported on Windows, as they were before. For non-Windows targets they error if the architecture does not match. Moving the check into AST lowering **is itself a breaking change in rare cases**, above and beyond the cases rustc currently warns about. See "Why or Why Not" for details. ## How We modify rustc_ast_lowering to prevent unsupported ABIs from leaking through the HIR without being checked for target support. Previously ad-hoc checking on various HIR items required making sure we check every HIR item which could contain an `extern "{abi}"` string. This is a losing proposition compared to gating the lowering itself. As a consequence, unsupported ABI strings will now hard-error instead of triggering the FCW `unsupported_fn_ptr_calling_conventions`. However, per #86232 this does cause errors for rare usages of `extern "{abi}"` that were theoretically possible to write in Rust source, without previous warning or error. For instance, trait declarations without impls were never checked. These are the exact kinds of leakages that this new approach prevents. This differs from the following PRs: - #141435 is orthogonal, as it adds a new lint for ABIs we have not warned on and are not touched by this PR - #141877 is subsumed by this, in that this simply cuts out bad functionality instead of adding epicycles for stable code ## Why or Why Not We already made the decision to issue the `unsupported_fn_ptr_calling_conventions` future compatibility warning. It has warned in dependencies since #135767, which reached stable with Rust 1.87. That was released on 2025 May 17, and it is now June. As we already had erred on these ABI strings in most other positions, and warn on stable for function pointer types, this breakage has had reasonable foreshadowing. Upgrading the warning to an error addresses a real problem. In some cases the Rust compiler can attempt to actually compute the ABI for calling a function. We could accept this case and compute unsupported ABIs according to some other ABI, silently[^0]. However, this obviously exposes Rust to errors in codegen. We cannot lower directly to the "obvious" ABI and then trust code generators like LLVM to reliably error on these cases, either. Refactoring the compiler so we could defer more ABI computations would be possible, but seems weakly motivated. Even if we succeeded, we would at minimum risk: - exposing the "whack-a-mole" problem but "approaching linking" instead of "leaving AST" - making it harder to reason about functions we *can* lower further - complicating the compiler for no clear benefit A deprecation cycle for the edge-cases could be implemented first, but it is not very useful for such marginal cases, like this trait declaration without a definition: ```rust pub trait UsedToSneakBy { pub extern "gpu-kernel" fn sneaky(); } ``` Upon any impl, even for provided fn within trait declarations, e.g. `pub extern "gpu-kernel" fn sneaky() {}`, different HIR types were used which would, in fact, get checked. Likewise with anything with function pointers. Thus we would be discussing deprecation cycles for code that is impotent or forewarned[^2]. Implementing a deprecation cycle _is_ possible, but it would likely require emitting multiple of a functionally identical warning or error on code that would not have multiple warnings or errors before. It is also not clear to me we would not find **another**, even more marginal edge-case that slipped through, as "things slip through" is the motivation for checking earlier. Additional effort spent on additional warnings should require committing to a hard limit first. r? lang Fixes #86232 Fixes #132430 Fixes #138738 Fixes #142107 [`unsupported_fn_ptr_calling_conventions`]: #130260 [^1]: Some already will not compile, due to reaching ICEs or LLVM errors. [^0]: We already do this for all `AbiStr` we cannot parse, pretending they are `ExternAbi::Rust`, but we also emit an error to prevent reaching too far into codegen. [^2]: It actually did appear in two cases in rustc's test suite because we are a collection of Rust edge-cases by the simple fact that we don't care if the code actually runs. These cases were excised in c1db989.
@craterbot run mode=build-only name=pr-142134-abi-ast-error cap-lints=warn |
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
🎉 Experiment
|
I'll send the necessary PRs to The primary culprit seems to be impls on function pointers, which are already warned on: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=e1e8ca91878cbbcba5c6dc3b064cea2b on
|
☔ The latest upstream changes (presumably #141435) made this pull request unmergeable. Please resolve the merge conflicts. |
c1db989
to
9981fee
Compare
extern "{abi}"
when the target does not support itextern "{abi}"
consistently in all positions
extern "{abi}"
consistently in all positionsextern "{abi}"
s consistently in all positions
This comment has been minimized.
This comment has been minimized.
1595699
to
4306efa
Compare
So what does this do with invalid ABIs in traits? I can't see a new lint being introduced here -- but making this a hard error immediately would be a breaking change, no? |
|
||
impl Test { | ||
pub extern "gpu-kernel" fn test(val: &str) {} | ||
//~^ ERROR [E0570] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error codes are pretty meaningless, making the test file hard to read. IMO it'd be better to have a snippet of the error message in the test file here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All these tests seem to assume that "gpu-kernel" is not a valid ABI. But that's target-specific, so this will fail when the test suite is run on a GPU target, right?
Like the other unsupported ABI tests, I think this should use --target
and #![no_core]
(ideally with minicore) so that the test does not depend on what the host target happens to be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm, I mean technically GPUs shouldn't ever be hosts so I wouldn't think we would build tests for them, but then Aaron Hsu's codfns compiler exists so maybe I shouldn't think that.
Correct. My proposal to lang is that they should consider it a functional non-event, because it truly seems to be. All the crater regressions are from doing things with function pointers, not unimplementable associated functions of traits. We have been writing these lints and missing cases every time, even though we thought we covered our bases last time. I think we should cut out the problem at the root. If they think we should write another lint, that's... probably fine, actually, but how many more times are we going to do this? I'd like to have a commitment to Obviously, I think 0 would be a reasonable number of additional repetitions, but there's other integers I could see myself happy with. @rustbot label: I-lang-nominated |
Did we? #86232 was very clear about affecting traits and fn ptrs, and the fn ptr lint is very clear about only checking fn ptrs. I don't think anyone was confused about the trait case, it's just that so far nobody felt like implementing the lint for that. |
err.emit(); | ||
} | ||
// already erred in rustc_ast_lowering | ||
AbiMapping::Invalid => (), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the history of leaky checks, maybe add a delay_span_bug here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
tests/ui/abi/unsupported.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add some trait declarations here? That can replace at least one of your new tests, too.
)); | ||
} | ||
} | ||
|
||
pub fn check_abi(tcx: TyCtxt<'_>, hir_id: hir::HirId, span: Span, abi: ExternAbi) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand this PR correctly, we still do not call this function for trait declarations. This means that UNSUPPORTED_CALLING_CONVENTIONS is not emitted for them, and so moving cdecl and friends to Invalid
will be another sudden breaking change with no prior lint. Is that right?
How hard would it be to find a place in hir_analysis (or wherever) to call this on trait declarations, too?
"Nobody felt like it" is kind of the problem. I have been triaging issues up and down the issue tracker for years, spending large amounts of time tagging and sorting issues, and increasingly trying to focus on understanding and collecting all the ones specifically related to ABI. Talking with you, even!... and ~last week was the first time I saw it. Even the people who know about it have bigger fish to fry. There is exactly zero code on crater that is affected by the edge-case breakage in practice. Usually, even the most obscure breakage at least hits some random unfinished project on GitHub, and then we decide whether to write off some abandoned code from 2017 or not, but the breakage I saw was purely about function pointers. If people insist on a performance then I will be their Pierrot, but not before the ringmaster directs me. They know well I have more interesting acts I could be rehearsing. |
0 crater regressions is a good sign, I agree. Crater doesn't see all code though, and it only builds for Linux (which is quite relevant when talking about target-specific behavior such as this). |
☔ The latest upstream changes (presumably #142299) made this pull request unmergeable. Please resolve the merge conflicts. |
We modify rustc_ast_lowering to prevent all unsupported ABIs from leaking through the HIR without being checked for target support. Previously ad-hoc checking on various HIR items required making sure we check every HIR item which could contain an `extern "{abi}"` string. This is a losing proposition compared to gating the lowering itself. As a consequence, unsupported ABI strings will now hard-error instead of triggering the FCW `unsupported_fn_ptr_calling_conventions`. This FCW was upgraded to warn in dependencies in Rust 1.87 which was released on 2025 May 17, and it is now 2025 June, so it has become active within a stable Rust version. As we already had errored on these ABIs in most other positions, and have warned for fn ptrs, this breakage has had reasonable foreshadowing. However, this does cause errors for usages of `extern "{abi}"` that were theoretically writeable within source but could not actually be applied in any useful way by Rust programmers without either warning or error. For instance, trait declarations without impls were never checked. These are the exact kinds of leakages that this new approach prevents. A deprecation cycle is not useful for these marginal cases as upon impl, even default impls within traits, different HIR objects would be used. Details of our HIR analysis meant that those objects did get checked.
We move the vectorcall ABI tests into their own file which is now only run on x86-64, while replacing them with rust-cold ABI tests so that aarch64 hosts continue to test an unstable ABI. A better solution might be cross-compiling or something but I really don't have time for that right now.
…ABIs Co-authored-by: Ralf Jung <[email protected]>
6289fce
to
fa0169e
Compare
Modify the handling of
extern "{abi}"
in the compiler so that it has consistent errors without regard to the position in the grammar.What
Implement the following breakages:
unsupported_fn_ptr_calling_conventions
from a warning to a hard errorIn particular, these architecture-specific ABIs now only compile on their architectures1:
ABIs that are logically x86-specific but actually permitted on all Windows targets remain permitted on Windows, as before. For non-Windows targets, they error if we had previously done so in other positions.
How
We modify rustc_ast_lowering to prevent unsupported ABIs from leaking through the HIR without being checked for target support to emit hard errors for every case where we would return
Invalid
fromAbiMap::canonize_abi
. Previously ad-hoc checking on various HIR items required making sure we check every HIR item which could contain anextern "{abi}"
string. This is a losing proposition compared to gating the lowering itself.As a consequence, unsupported ABI strings will now hard-error instead of triggering the FCW
unsupported_fn_ptr_calling_conventions
. The code is also simpler compared to some cases which split on unstable versus stable, only suffering some unavoidable complication to support the newly-revivedunsupported_calling_conventions
lint.2However, per #86232 this does cause errors for rare usages of
extern "{abi}"
that were theoretically possible to write in Rust source, without previous warning or error. For instance, trait declarations without impls were never checked. These are the exact kinds of leakages that this new approach prevents.This differs from the following PRs:
unsupported_calling_conventions
lint to reject more invalid calling conventions #141435 is orthogonal, as it adds a new lint for ABIs we have not warned on and are not touched by this PRWhy or Why Not
We already made the decision to issue the
unsupported_fn_ptr_calling_conventions
future compatibility warning. It has warned in dependencies since #135767, which reached stable with Rust 1.87. That was released on 2025 May 17, and it is now June. As we already had erred on these ABI strings in most other positions, and warn on stable for function pointer types, this breakage has had reasonable foreshadowing.Upgrading the warning to an error addresses a real problem. In some cases the Rust compiler can attempt to actually compute the ABI for calling a function. We could accept this case and compute unsupported ABIs according to some other ABI, silently3. However, this obviously exposes Rust to errors in codegen. We cannot lower directly to the "obvious", target-incorrect ABI and then trust code generators like LLVM to reliably error on these cases, either.
Other considerations include:
unsupported_calling_conventions
lint, means people who would otherwise have to deal with two lints only have to update their code in one batch. Of course, one of them is as breakage.r? lang
Fixes #86232
Fixes #132430
Fixes #138738
Fixes #142107
Footnotes
Some already will not compile, due to reaching ICEs or LLVM errors. ↩
That lint cannot be moved in a similar way yet because lints operate on HIR, so you cannot emit lints when the HIR has not been completely formed. ↩
We already do this for all
AbiStr
we cannot parse, pretending they areExternAbi::Rust
, but we also emit an error to prevent reaching too far into codegen. ↩Upon any impl, even for provided fn within trait declarations, e.g.
pub extern "gpu-kernel" fn sneaky() {}
, different HIR types were used which would, in fact, get checked. Likewise for anything with function pointers. Thus we would be discussing deprecation cycles for code that is impotent or forewarned5. ↩It actually did appear in two cases in rustc's test suite because we are a collection of Rust edge-cases by the simple fact that we don't care if the code actually runs. These cases are being excised in 643a9d2 ↩