-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add extern "custom"
#3980
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?
Add extern "custom"
#3980
Changes from all commits
bc0c28d
b38e056
231b22f
eba79be
5573cea
206ea92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m looking at RFC 2972
In particular the middle part
seems very relevant IMHO to the discussion of allowing arguments, or allowing non- But I also don’t think this is really how Rust works. If there are still manually-upheld preconditions (not expressed in the type signature) then it simply must be I believe that to really allow a I also believe that allowing function arguments would - practically - already allow a way to do this. People could establish a convention where the first argument to a let f: extern "custom" fn(MyAbi, x: u32, y: u32) -> u32;could be sensibly be considered fully safe. They could be safe in the sense that you can then provide a sound way to call them from safe code, e.g. as a callback: fn call_my_abi(callback: extern "custom" fn(MyAbi, u32, u32) -> u32, x: u32, y: u32) -> u32 { … }
fn main() {
let f: extern "custom" fn(MyAbi, x: u32, y: u32) -> u32 = todo!();
let n: u32 = call_my_abi(f, 42, 123); // yay, no unsafe!
}I also think that establishing such a convention is somewhat questionable for downstream users to do (without any official “blessing” of the practice), since different conventions could be incompatible.1 Furthermore, if we ever wanted to have a different, more “dedicated” mechanism for specifying custom ABIs on a type level in the future2, then it’d be incompatible with this kind of approach. I personally wouldn’t dislike the idea of just blessing this kind of approach though, and to leave any sort of better support for it up to future possibilities. Speaking of future possibility: I could even imagine a trait like Alternatively, if we don’t want to discuss any of this at this point, it would in my opinion make a lot of sense to uphold the restriction of only Footnotes
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There's currently an experimental implementation of |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,168 @@ | ||||||||||
| - Feature Name: `abi_custom` | ||||||||||
| - Start Date: 2026-07-01) | ||||||||||
| - RFC PR: [rust-lang/rfcs#3980](https://github.com/rust-lang/rfcs/pull/3980) | ||||||||||
| - Rust Issue: [rust-lang/rust#140829)](https://github.com/rust-lang/rust/issues/140829) | ||||||||||
|
|
||||||||||
| ## Summary | ||||||||||
| [summary]: #summary | ||||||||||
|
|
||||||||||
| An `extern "custom" fn` is a function with a custom ABI that is unknown to rust. Often these are low-level functions that pass arguments in different registers than any standard calling convention, so using this helps rustc block you from using this in any place where rustc would need to understand that calling convention. | ||||||||||
|
|
||||||||||
|
|
||||||||||
| ```rust | ||||||||||
| /// # SAFETY | ||||||||||
| /// | ||||||||||
| /// - Expects the dividend and the divisor in r0 and r1. | ||||||||||
| /// - Returns the quotient in r0 and the remainder in r1. | ||||||||||
| #[unsafe(naked)] | ||||||||||
| pub unsafe extern "custom" fn __aeabi_uidivmod() { | ||||||||||
|
folkertdev marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Since there's no parameter list, and you can't use a call expression with an Another option is to use the variadic dots syntax:
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removing the making it
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. -1 for removing the parens entirely, seems not worth changing the grammar for. +0.5 for
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Variadic arguments are passed differently from regular arguments on some calling conventions though.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that this is a niche feature, that almost nobody will see much less use, I see no reason to spend any syntax budget here. it's funny/frustrating how so many RFC threads devolve into discussions about syntax: everyone can have an opinion about syntax. The bar for adding syntax is, very deliberately, extremely high.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @folkertdev it seems to be human nature or sth. Wadler's law in full effect 😄 |
||||||||||
| core::arch::naked_asm!( | ||||||||||
| "push {{lr}}", | ||||||||||
| "sub sp, sp, #4", | ||||||||||
| "mov r2, sp", | ||||||||||
| "bl {trampoline}", | ||||||||||
| "ldr r1, [sp]", | ||||||||||
| "add sp, sp, #4", | ||||||||||
| "pop {{pc}}", | ||||||||||
| trampoline = sym crate::arm::__udivmodsi4 | ||||||||||
| ); | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be worth it to allow the If we allow an explicit parameter list as suggested in #3980, perhaps we could even allow specifying inputs and outputs that reference those parameters (and also the return place?) |
||||||||||
| } | ||||||||||
|
|
||||||||||
| unsafe extern "custom" { | ||||||||||
| fn __fentry__(); | ||||||||||
| } | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ### History | ||||||||||
|
|
||||||||||
| * Proposal: https://github.com/rust-lang/rust/issues/140566 | ||||||||||
| * Tracking issue: https://github.com/rust-lang/rust/issues/140829 | ||||||||||
| * Implementation: https://github.com/rust-lang/rust/pull/140770 | ||||||||||
| * Stabilization PR: https://github.com/rust-lang/rust/pull/158504 | ||||||||||
|
|
||||||||||
| ## Motivation | ||||||||||
| [motivation]: #motivation | ||||||||||
|
|
||||||||||
| In some low-level scenarios we must define naked functions with a custom ABI. This comes up in `rust-lang/compiler-builtins` (e.g. for `__rust_probestack` and `__aeabi_uidivmod`), and also in systems programming (e.g. when defining `__fentry__`, a symbol used the mcount mechanism). | ||||||||||
|
|
||||||||||
| The current solution is often to use `extern "C"`, but that is misleading: the function does not use the C calling convention, and calling it as if it were is almost certainly causing UB. | ||||||||||
|
|
||||||||||
| ## Guide-level explanation | ||||||||||
| [guide-level-explanation]: #guide-level-explanation | ||||||||||
|
|
||||||||||
| Because rust doesn't know what calling convention to use, an `extern "custom"` function can only be called via inline assembly or FFI. | ||||||||||
|
|
||||||||||
| ``` | ||||||||||
| error: functions with the "custom" ABI cannot be called | ||||||||||
| --> <source>:5:5 | ||||||||||
| | | ||||||||||
| 5 | bar(); | ||||||||||
| | ^^^^^ | ||||||||||
| | | ||||||||||
| note: an `extern "custom"` function can only be called using inline assembly | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| An `extern "custom"` function definition must be a naked function: | ||||||||||
|
|
||||||||||
| ``` | ||||||||||
| error: items with the "custom" ABI can only be declared externally or defined via naked functions | ||||||||||
| --> <source>:10:1 | ||||||||||
| | | ||||||||||
| 10 | unsafe extern "custom" fn bar() { | ||||||||||
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||||||||
| | | ||||||||||
| help: convert this to an `#[unsafe(naked)]` function | ||||||||||
| | | ||||||||||
| 10 + #[unsafe(naked)] | ||||||||||
| 11 | unsafe extern "custom" fn bar() { | ||||||||||
| | | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| An `extern "custom"` function definition must be unsafe. The intent here is that a safety comment is written on how this function may be called. | ||||||||||
|
|
||||||||||
| ``` | ||||||||||
| error: functions with the "custom" ABI must be unsafe | ||||||||||
| --> <source>:10:1 | ||||||||||
| | | ||||||||||
| 10 | extern "custom" fn bar() { | ||||||||||
| | ^^^^^^^^^^^^^^^^^^^^^^^^ | ||||||||||
| | | ||||||||||
| help: add the `unsafe` keyword to this definition | ||||||||||
| | | ||||||||||
| 10 | unsafe extern "custom" fn bar() { | ||||||||||
| | ++++++ | ||||||||||
| ``` | ||||||||||
|
Comment on lines
+81
to
+94
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since all ways of actually calling an
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The justification to me seems to be to encourage people to write safety comments describing the actual ABI, but I actually kind of agree and think we could have a separate clippy lint for documenting calling conventions for Also from the perspective of, should these function definitions be allowed with
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If an
Maybe that mandatory
But this argument applies to defining ordinary unsafe functions too, yet this will fire the lint #[expect(unsafe_code)]
unsafe fn just_defining_a_function_not_calling_it() {}So unless we further refine
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we change the unsafe requirements, lines 96-109 would also be changed. That's not a strong justification either way.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah. I guess for For instance, using /// Multiplies two 64-bit integers.
///
/// # ABI
///
/// The first argument is passed across registers r0 (lower 32 bits) and r1
/// (upper 32 bits). The second argument spans registers r2 and r3. It returns
/// the final 64-bit result across r0 and r1.
#[naked]
pub extern "custom" fn __aeabi_lmul(multiplier: u64, multiplicand: u64) -> u64 {
...
}
/// Divides two 32-bit integers, computing both the quotient and remainder.
///
/// # ABI
///
/// The numerator (top number) and denominator (bottom number) are passed into
/// CPU registers r0 and r1. It returns the quotient in register r0 and the
/// remainder in register r1.
///
/// # Safety
///
/// The divisor must be non-zero.
#[naked]
pub unsafe extern "custom" fn __aeabi_uidivmod(numerator: u32, denominator: u32) -> (u32, u32) {
...
}Here, we make Of course, neither the argument lists or whether it's marked unsafe has any real effect here except insofar it helps the reader understand the function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll remake the argument above here, these annotations make the functions easier for a reader who just wants to know what some code it doing, and doesn't care about the specifics of the ABI. I think, since it has no effect on the usage of the function, I see no reason to disallow it or add special handling just to deny |
||||||||||
|
|
||||||||||
| In an `extern "custom"` block, functions cannot be marked as `safe`: | ||||||||||
|
|
||||||||||
| ``` | ||||||||||
| error: foreign functions with the "custom" ABI cannot be safe | ||||||||||
| --> <source>:16:5 | ||||||||||
| | | ||||||||||
| 16 | safe fn foobar(); | ||||||||||
| | ^^^^^^^^^^^^^^^^^ | ||||||||||
| | | ||||||||||
| help: remove the `safe` keyword from this definition | ||||||||||
| | | ||||||||||
| 16 - safe fn foobar(); | ||||||||||
| 16 + fn foobar(); | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| An `extern "custom"` function cannot have any arguments or a return type: | ||||||||||
|
|
||||||||||
| ``` | ||||||||||
| error: invalid signature for `extern "custom"` function | ||||||||||
| --> <source>:6:31 | ||||||||||
| | | ||||||||||
| 6 | unsafe extern "custom" fn foo(a: i32) -> i32 { | ||||||||||
| | ^^^^^^ ^^^ | ||||||||||
|
Comment on lines
+111
to
+118
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in the current implementation in 1.98.0-nightly (2026-07-01 4c9d2bfe4ad7a6566909) returning #![feature(abi_custom, never_type)]
use std::arch::naked_asm;
#[unsafe(naked)]
unsafe extern "custom" fn foo() -> ! { // no error.
naked_asm!("ud2")
}
#[unsafe(naked)]
unsafe extern "custom" fn bar() -> (!) { // error: invalid signature for `extern "custom"` function
naked_asm!("ud2")
}
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This just feels like a bug IMHO, since no ABI means no way to tell if the function never returns. That said, technically, since crates using this method would benefit from knowing this, perhaps it is a reason to allow it, perhaps via some
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, is there a reason it needs to care that you wrote arguments? Since you can't call it anyway, would it make more sense to just say "sure, write whatever arguments is helpful" and rust will just not use them for anything (other than rustdoc)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with @scottmcm here, since we can't make a direct call, why should we limit the signature? I don't see any disadvantages to adding arbitrary parameters and return types, but personally, I find it easier to parse
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, the function still has semantic inputs and outputs, even if the compiler doesn't know the ABI for them, so we should prefer that the function show those inputs and outputs even if only as documentation. |
||||||||||
| | | ||||||||||
| = note: functions with the "custom" ABI cannot have any parameters or return type | ||||||||||
| help: remove the parameters and return type | ||||||||||
| | | ||||||||||
| 6 - unsafe extern "custom" fn foo(a: i32) -> i32 { | ||||||||||
| 6 + unsafe extern "custom" fn foo() { | ||||||||||
| | | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ## Reference-level explanation | ||||||||||
| [reference-level-explanation]: #reference-level-explanation | ||||||||||
|
|
||||||||||
| See https://github.com/rust-lang/reference/pull/2300. | ||||||||||
|
|
||||||||||
| ## Drawbacks | ||||||||||
| [drawbacks]: #drawbacks | ||||||||||
|
|
||||||||||
| No specific drawback. | ||||||||||
|
|
||||||||||
| ## Rationale and alternatives | ||||||||||
| [rationale-and-alternatives]: #rationale-and-alternatives | ||||||||||
|
|
||||||||||
| https://github.com/rust-lang/rust/issues/140566 | ||||||||||
|
|
||||||||||
| The name has already been debated. The name `unknown` has been mentioned, but to write the implementation you really do need to know the ABI. It is custom in the sense that rustc does not know about it, but the author definitely does. | ||||||||||
|
|
||||||||||
|
folkertdev marked this conversation as resolved.
|
||||||||||
| Arguments and return types are forbidden because they are not consumed in any way. The compiler has no way of validating them against the function body, similar to other naked functions, but the functions can never be invoked directly so they serve no purpose when calling. The intent is that correct parameter passing and returning will be covered in documentation. | ||||||||||
|
|
||||||||||
| ## Prior art | ||||||||||
| ======= | ||||||||||
| [prior-art]: #prior-art | ||||||||||
|
|
||||||||||
| https://github.com/rust-lang/rust/issues/140566#issuecomment-2846205457 | ||||||||||
|
|
||||||||||
| We do have some other calling conventions that cannot be called using rust's function calling syntax: | ||||||||||
|
|
||||||||||
| - extern "*-interrupt` | ||||||||||
| - extern "gpu-kernel" | ||||||||||
|
|
||||||||||
| Those however do have a known ABI, it's just that semantically it does not make sense to call them from a rust program. | ||||||||||
|
|
||||||||||
| ## Unresolved questions | ||||||||||
| [unresolved-questions]: #unresolved-questions | ||||||||||
|
|
||||||||||
| None currently. | ||||||||||
|
|
||||||||||
| ## Future possibilities | ||||||||||
| [future-possibilities]: #future-possibilities | ||||||||||
|
|
||||||||||
| None currently. | ||||||||||
Uh oh!
There was an error while loading. Please reload this page.
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.
Are
extern "custom"function pointers allowed? The current implementation on nightly considersunsafe extern "custom" fn(),extern "custom" fn(), andextern "custom" fn(i32) -> u64to all be valid types.One option would be to have a single bare
extern "custom"function pointer type, to represent a function with known address but unknown calling convention. All other function pointers would be able to coerce to it.View changes since the review
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.
Good catch! IMHO, it would make sense if you can only define function items as
extern "custom"when they'reunsafe, argument-less and returning()(or!), to apply the same kind of restrictions on whatfnpointer types are usable.