Skip to content

Lang proposal: extern "unspecified" for naked functions with arbitrary ABI #140566

Open
@tgross35

Description

@tgross35

Background

One of the uses of naked functions is to implement custom calling conventions. We have some code in compiler-builtins like this:

// NOTE This function and the ones below are implemented using assembly because they are using a
// custom calling convention which can't be implemented using a normal Rust function.
#[unsafe(naked)]
pub unsafe extern "C" fn __aeabi_uidivmod() {
    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
    );
}

The ABI needs to be specified, so extern "C" is used. However, this is misleading as the function does not actually use the C calling convention.

Correct ABI would be considered part of the preconditions for this function and it would only be callable inside an unsafe block, but Rust has no way to call the function correctly so it seems like we should prevent this.

Proposal

Add a new "unspecified" ABI that may be used with naked functions. Rust will error on attempts to call them.

/// # Safety
///
/// This function implements a custom calling convention that requires the
/// following inputs:
/// * `r8` contains a pointer
/// * `r9` contains a length
/// The pointer in `r8` must be valid for reads up to `r9` bytes.
///
/// `r8` and `r9` are clobbered but no other registers are.
#[unsafe(naked)]
pub unsafe extern "unspecified" fn foo() {
    core::arch::naked_asm!(
        // ...
    );
}

// SAFETY: `bar` is provided by `libbar.a` which we link.
unsafe extern "unspecified" {
    fn bar();
}

fn call_foo(buf: &[u8]) {
    // SAFETY: I didn't read the docs
    unsafe {
        foo();
        //~^ ERROR: `foo` has an unspecified ABI and cannot be called directly
        bar();
        //~^ ERROR: `bar` has an unspecified ABI and cannot be called directly
    }

    // SAFETY: call `foo` with its specified ABI, account for r8 & r9 clobbers
    unsafe {
        core::arch::asm!(
            "mov r8 {ptr}",
            "mov r9 {len}",
            "call {foo}",
            ptr = in(reg) buf.as_ptr(),
            len = in(reg) buf.len(),
            out("r8") _,
            out("r9") _,
            foo = sym foo,
        )
    }
}

Proposed rules:

  1. extern "unspecified" can only be used with naked functions or extern blocks
  2. The compiler will reject calling any functions marked extern "unspecified". It can still be passed as a function pointer, and it can be a sym in an asm block.
  3. extern "unspecified" functions must be marked unsafe, and cannot be safe fn with an extern block. This is a hard error. (I'm less certain about this rule since unsafety doesn't mean much if you can't call it. Proposed because it seems consistent with how it must be used, given the function still has preconditions, and it's probably makes sense to treat them as unsafe in the compiler.)

Questions:

  1. What should it be named? "unspecified", "none", "any", and "unknown" all seem workable. Also suggested in this thread: "custom", "uncallable".
  2. Should parameters also be rejected? If the function is not callable, they don't serve much purpose other than documentation.

cc @rust-lang/lang, @folkertdev, @Amanieu

(currently empty) thread for discussion on Zulip: https://rust-lang.zulipchat.com/#narrow/channel/216763-project-inline-asm/topic/.60extern.20.22unspecified.22.60.20for.20naked.20functions/with/515596073

Metadata

Metadata

Assignees

Labels

A-inline-assemblyArea: Inline assembly (`asm!(…)`)C-feature-requestCategory: A feature request, i.e: not implemented / a PR.I-lang-radarItems that are on lang's radar and will need eventual work or consideration.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions