Skip to content

add extern "custom" functions #140770

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_abi/src/canon_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub enum CanonAbi {
Rust,
RustCold,

/// An ABI that rustc does not know how to call or define.
Custom,

/// ABIs relevant to 32-bit Arm targets
Arm(ArmCall),
/// ABI relevant to GPUs: the entry point for a GPU kernel
Expand Down Expand Up @@ -57,6 +60,7 @@ impl fmt::Display for CanonAbi {
CanonAbi::C => ExternAbi::C { unwind: false },
CanonAbi::Rust => ExternAbi::Rust,
CanonAbi::RustCold => ExternAbi::RustCold,
CanonAbi::Custom => ExternAbi::Custom,
CanonAbi::Arm(arm_call) => match arm_call {
ArmCall::Aapcs => ExternAbi::Aapcs { unwind: false },
ArmCall::CCmseNonSecureCall => ExternAbi::CCmseNonSecureCall,
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_abi/src/extern_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ pub enum ExternAbi {
/// Even normally-compatible Rust types can become ABI-incompatible with this ABI!
Unadjusted,

/// An ABI that rustc does not know how to call or define. Functions with this ABI can
/// only be created using `#[naked]` functions or `extern "custom"` blocks, and can only
/// be called from inline assembly.
Custom,

/// UEFI ABI, usually an alias of C, but sometimes an arch-specific alias
/// and only valid on platforms that have a UEFI standard
EfiApi,
Expand Down Expand Up @@ -141,6 +146,7 @@ abi_impls! {
AvrNonBlockingInterrupt =><= "avr-non-blocking-interrupt",
Cdecl { unwind: false } =><= "cdecl",
Cdecl { unwind: true } =><= "cdecl-unwind",
Custom =><= "custom",
EfiApi =><= "efiapi",
Fastcall { unwind: false } =><= "fastcall",
Fastcall { unwind: true } =><= "fastcall-unwind",
Expand Down
31 changes: 31 additions & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3520,6 +3520,37 @@ impl FnHeader {
|| matches!(constness, Const::Yes(_))
|| !matches!(ext, Extern::None)
}

pub fn span(&self) -> Option<Span> {
Copy link
Contributor

Choose a reason for hiding this comment

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

/// Return a span encompassing the header, or none if all options are default.

Or similar

fn append(a: &mut Option<Span>, b: Span) {
*a = match a {
None => Some(b),
Some(x) => Some(x.to(b)),
}
}

let mut full_span = None;

match self.safety {
Safety::Unsafe(span) | Safety::Safe(span) => append(&mut full_span, span),
Safety::Default => {}
};

if let Some(coroutine_kind) = self.coroutine_kind {
append(&mut full_span, coroutine_kind.span());
}

if let Const::Yes(span) = self.constness {
append(&mut full_span, span);
}

match self.ext {
Extern::Implicit(span) | Extern::Explicit(_, span) => append(&mut full_span, span),
Extern::None => {}
}

full_span
}
}

impl Default for FnHeader {
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_ast_lowering/src/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,8 @@ pub fn extern_abi_stability(abi: ExternAbi) -> Result<(), UnstableAbi> {
feature: sym::cmse_nonsecure_entry,
explain: GateReason::Experimental,
}),
ExternAbi::Custom => {
Err(UnstableAbi { abi, feature: sym::abi_custom, explain: GateReason::Experimental })
}
}
}
13 changes: 13 additions & 0 deletions compiler/rustc_ast_passes/messages.ftl
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
ast_passes_abi_custom_invalid_signature =
invalid signature for `extern "custom"` function
.note = functions with the `"custom"` ABI cannot have any parameters or return type
.suggestion = remove the parameters and return type
ast_passes_abi_custom_safe_foreign_function =
foreign functions with the `"custom"` ABI cannot be safe
.suggestion = remove the `safe` keyword from this definition
ast_passes_abi_custom_safe_function =
functions with the `"custom"` ABI must be unsafe
.suggestion = add the `unsafe` keyword to this definition
ast_passes_assoc_const_without_body =
associated constant in `impl` without body
.suggestion = provide a definition for the constant
Expand Down
74 changes: 70 additions & 4 deletions compiler/rustc_ast_passes/src/ast_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

use std::mem;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;

use itertools::{Either, Itertools};
use rustc_abi::ExternAbi;
Expand Down Expand Up @@ -81,6 +82,7 @@ struct AstValidator<'a> {

/// Used to ban explicit safety on foreign items when the extern block is not marked as unsafe.
extern_mod_safety: Option<Safety>,
extern_mod_abi: Option<ExternAbi>,

lint_node_id: NodeId,

Expand Down Expand Up @@ -121,10 +123,17 @@ impl<'a> AstValidator<'a> {
self.outer_trait_or_trait_impl = old;
}

fn with_in_extern_mod(&mut self, extern_mod_safety: Safety, f: impl FnOnce(&mut Self)) {
let old = mem::replace(&mut self.extern_mod_safety, Some(extern_mod_safety));
fn with_in_extern_mod(
&mut self,
extern_mod_safety: Safety,
abi: Option<ExternAbi>,
f: impl FnOnce(&mut Self),
) {
let old_safety = mem::replace(&mut self.extern_mod_safety, Some(extern_mod_safety));
let old_abi = mem::replace(&mut self.extern_mod_abi, abi);
f(self);
self.extern_mod_safety = old;
self.extern_mod_safety = old_safety;
self.extern_mod_abi = old_abi;
}

fn with_tilde_const(
Expand Down Expand Up @@ -370,6 +379,50 @@ impl<'a> AstValidator<'a> {
}
}

/// An `extern "custom"` function must be unsafe, and must not have any parameters or return
/// type.
fn check_custom_abi(&self, ctxt: FnCtxt, ident: &Ident, sig: &FnSig) {
let dcx = self.dcx();

// An `extern "custom"` function must be unsafe.
match sig.header.safety {
Safety::Unsafe(_) => { /* all good */ }
Safety::Safe(safe_span) => {
let safe_span =
self.sess.psess.source_map().span_until_non_whitespace(safe_span.to(sig.span));
dcx.emit_err(errors::AbiCustomSafeForeignFunction { span: sig.span, safe_span });
}
Safety::Default => match ctxt {
FnCtxt::Foreign => { /* all good */ }
FnCtxt::Free | FnCtxt::Assoc(_) => {
self.dcx().emit_err(errors::AbiCustomSafeFunction {
span: sig.span,
unsafe_span: sig.span.shrink_to_lo(),
});
}
},
}

// An `extern "custom"` function must not have any parameters or return type.
let mut spans: Vec<_> = sig.decl.inputs.iter().map(|p| p.span).collect();
if let FnRetTy::Ty(ref ret_ty) = sig.decl.output {
spans.push(ret_ty.span);
}

if !spans.is_empty() {
let header_span = sig.header.span().unwrap_or(sig.span.shrink_to_lo());
let suggestion_span = sig.span.with_lo(header_span.hi());
let padding = if header_span.is_empty() { "" } else { " " };

self.dcx().emit_err(errors::AbiCustomInvalidSignature {
spans,
symbol: ident.name,
suggestion_span,
padding,
});
}
}

/// This ensures that items can only be `unsafe` (or unmarked) outside of extern
/// blocks.
///
Expand Down Expand Up @@ -1005,7 +1058,9 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
if abi.is_none() {
self.handle_missing_abi(*extern_span, item.id);
}
self.with_in_extern_mod(*safety, |this| {

let extern_abi = abi.and_then(|abi| ExternAbi::from_str(abi.symbol.as_str()).ok());
self.with_in_extern_mod(*safety, extern_abi, |this| {
visit::walk_item(this, item);
});
self.extern_mod_span = old_item;
Expand Down Expand Up @@ -1145,6 +1200,9 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
self.check_foreign_fn_bodyless(*ident, body.as_deref());
self.check_foreign_fn_headerless(sig.header);
self.check_foreign_item_ascii_only(*ident);
if self.extern_mod_abi == Some(ExternAbi::Custom) {
self.check_custom_abi(FnCtxt::Foreign, ident, sig);
}
}
ForeignItemKind::TyAlias(box TyAlias {
defaultness,
Expand Down Expand Up @@ -1352,6 +1410,13 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
self.check_item_safety(span, safety);
}

if let FnKind::Fn(ctxt, _, fun) = fk
&& let Extern::Explicit(str_lit, _) = fun.sig.header.ext
&& let Ok(ExternAbi::Custom) = ExternAbi::from_str(str_lit.symbol.as_str())
{
self.check_custom_abi(ctxt, &fun.ident, &fun.sig);
}

self.check_c_variadic_type(fk);

// Functions cannot both be `const async` or `const gen`
Expand Down Expand Up @@ -1703,6 +1768,7 @@ pub fn check_crate(
outer_impl_trait_span: None,
disallow_tilde_const: Some(TildeConstReason::Item),
extern_mod_safety: None,
extern_mod_abi: None,
lint_node_id: CRATE_NODE_ID,
is_sdylib_interface,
lint_buffer: lints,
Expand Down
48 changes: 48 additions & 0 deletions compiler/rustc_ast_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -824,3 +824,51 @@ pub(crate) struct MissingAbi {
#[suggestion(code = "extern \"<abi>\"", applicability = "has-placeholders")]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(ast_passes_abi_custom_safe_foreign_function)]
pub(crate) struct AbiCustomSafeForeignFunction {
#[primary_span]
pub span: Span,

#[suggestion(
ast_passes_suggestion,
applicability = "maybe-incorrect",
code = "",
style = "verbose"
)]
pub safe_span: Span,
}

#[derive(Diagnostic)]
#[diag(ast_passes_abi_custom_safe_function)]
pub(crate) struct AbiCustomSafeFunction {
#[primary_span]
pub span: Span,

#[suggestion(
ast_passes_suggestion,
applicability = "machine-applicable",
code = "unsafe ",
style = "verbose"
)]
pub unsafe_span: Span,
}

#[derive(Diagnostic)]
#[diag(ast_passes_abi_custom_invalid_signature)]
#[note]
pub(crate) struct AbiCustomInvalidSignature {
#[primary_span]
pub spans: Vec<Span>,

#[suggestion(
ast_passes_suggestion,
applicability = "machine-applicable",
code = "{padding}fn {symbol}()",
style = "verbose"
)]
pub suggestion_span: Span,
pub symbol: Symbol,
pub padding: &'static str,
}
5 changes: 5 additions & 0 deletions compiler/rustc_codegen_cranelift/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ pub(crate) fn conv_to_call_conv(
CanonAbi::Rust | CanonAbi::C => default_call_conv,
CanonAbi::RustCold => CallConv::Cold,

// Functions with this calling convention can only be called from assembly, but it is
// possible to declare an `extern "custom"` block, so the backend still needs a calling
// convention for declaring foreign functions.
CanonAbi::Custom => default_call_conv,
Comment on lines +54 to +57
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems reasonable but I wonder if down the line clif (or the other backends) might want to have some kind of calling convention representation that can be bug!ed on if it gets directly called somewhere. (cc @bjorn3)


CanonAbi::X86(x86_call) => match x86_call {
X86Call::SysV64 => CallConv::SystemV,
X86Call::Win64 => CallConv::WindowsFastcall,
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_codegen_gcc/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,16 @@ impl<'gcc, 'tcx> FnAbiGccExt<'gcc, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
pub fn conv_to_fn_attribute<'gcc>(conv: CanonAbi, arch: &str) -> Option<FnAttribute<'gcc>> {
let attribute = match conv {
CanonAbi::C | CanonAbi::Rust => return None,
CanonAbi::RustCold => FnAttribute::Cold,
// Functions with this calling convention can only be called from assembly, but it is
// possible to declare an `extern "custom"` block, so the backend still needs a calling
// convention for declaring foreign functions.
CanonAbi::Custom => return None,
CanonAbi::Arm(arm_call) => match arm_call {
ArmCall::CCmseNonSecureCall => FnAttribute::ArmCmseNonsecureCall,
ArmCall::CCmseNonSecureEntry => FnAttribute::ArmCmseNonsecureEntry,
ArmCall::Aapcs => FnAttribute::ArmPcs("aapcs"),
},
CanonAbi::RustCold => FnAttribute::Cold,
CanonAbi::GpuKernel => {
if arch == "amdgpu" {
FnAttribute::GcnAmdGpuHsaKernel
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_codegen_llvm/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,10 @@ impl llvm::CallConv {
match conv {
CanonAbi::C | CanonAbi::Rust => llvm::CCallConv,
CanonAbi::RustCold => llvm::PreserveMost,
// Functions with this calling convention can only be called from assembly, but it is
// possible to declare an `extern "custom"` block, so the backend still needs a calling
// convention for declaring foreign functions.
CanonAbi::Custom => llvm::CCallConv,
CanonAbi::GpuKernel => {
if arch == "amdgpu" {
llvm::AmdgpuKernel
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,8 @@ declare_features! (
(unstable, abi_avr_interrupt, "1.45.0", Some(69664)),
/// Allows `extern "C-cmse-nonsecure-call" fn()`.
(unstable, abi_c_cmse_nonsecure_call, "1.51.0", Some(81391)),
/// Allows `extern "custom" fn()`.
(unstable, abi_custom, "CURRENT_RUSTC_VERSION", Some(140829)),
/// Allows `extern "gpu-kernel" fn()`.
(unstable, abi_gpu_kernel, "1.86.0", Some(135467)),
/// Allows `extern "msp430-interrupt" fn()`.
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_hir_analysis/messages.ftl
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
hir_analysis_abi_custom_clothed_function =
functions with the `"custom"` ABI must be naked
.suggestion = add the `#[unsafe(naked)]` attribute to this function
Comment on lines +2 to +3
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's worth hinting at the other way to have a custom function. Not the best wording, but maybe something like:

    items with the `"custom"` ABI can only be declared externally or defined via naked functions
    .suggestion = convert this to an `#[unsafe(naked)]` function


hir_analysis_ambiguous_assoc_item = ambiguous associated {$assoc_kind} `{$assoc_ident}` in bounds of `{$qself}`
.label = ambiguous associated {$assoc_kind} `{$assoc_ident}`

Expand Down
14 changes: 13 additions & 1 deletion compiler/rustc_hir_analysis/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use rustc_middle::ty::error::TypeErrorToStringExt;
use rustc_middle::ty::layout::{LayoutError, MAX_SIMD_LANES};
use rustc_middle::ty::util::Discr;
use rustc_middle::ty::{
AdtDef, BottomUpFolder, GenericArgKind, RegionKind, TypeFoldable, TypeSuperVisitable,
AdtDef, BottomUpFolder, FnSig, GenericArgKind, RegionKind, TypeFoldable, TypeSuperVisitable,
TypeVisitable, TypeVisitableExt, fold_regions,
};
use rustc_session::lint::builtin::UNINHABITED_STATIC;
Expand Down Expand Up @@ -90,6 +90,18 @@ pub fn check_abi_fn_ptr(tcx: TyCtxt<'_>, hir_id: hir::HirId, span: Span, abi: Ex
}
}

pub fn check_custom_abi(tcx: TyCtxt<'_>, def_id: LocalDefId, fn_sig: FnSig<'_>, fn_sig_span: Span) {
if fn_sig.abi == ExternAbi::Custom {
// Function definitions that use `extern "custom"` must be naked functions.
if !tcx.has_attr(def_id, sym::naked) {
tcx.dcx().emit_err(crate::errors::AbiCustomClothedFunction {
span: fn_sig_span,
naked_span: tcx.def_span(def_id).shrink_to_lo(),
});
}
}
}

fn check_struct(tcx: TyCtxt<'_>, def_id: LocalDefId) {
let def = tcx.adt_def(def_id);
let span = tcx.def_span(def_id);
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/src/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub mod wfcheck;

use std::num::NonZero;

pub use check::{check_abi, check_abi_fn_ptr};
pub use check::{check_abi, check_abi_fn_ptr, check_custom_abi};
use rustc_abi::{ExternAbi, VariantIdx};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::{Diag, ErrorGuaranteed, pluralize, struct_span_code_err};
Expand Down
14 changes: 14 additions & 0 deletions compiler/rustc_hir_analysis/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1698,3 +1698,17 @@ pub(crate) struct SelfInTypeAlias {
#[label]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(hir_analysis_abi_custom_clothed_function)]
pub(crate) struct AbiCustomClothedFunction {
#[primary_span]
pub span: Span,
#[suggestion(
hir_analysis_suggestion,
applicability = "maybe-incorrect",
code = "#[unsafe(naked)]\n",
style = "short"
)]
pub naked_span: Span,
}
Loading
Loading