Macro r_efi::eficall

source ·
macro_rules! eficall {
    (@munch(($($prefix:tt)*),(pub $($suffix:tt)*))) => { ... };
    (@munch(($($prefix:tt)*),(unsafe $($suffix:tt)*))) => { ... };
    (@munch(($($prefix:tt)*),($($suffix:tt)*))) => { ... };
    ($($arg:tt)*) => { ... };
}
Expand description

Annotate function with UEFI calling convention

Since rust-1.68 you can use extern "efiapi" as calling-convention to achieve the same behavior as this macro. This macro is kept for backwards-compatibility only, but will nowadays map to extern "efiapi".

This macro takes a function-declaration as argument and produces the same function-declaration but annotated with the correct calling convention. Since the default extern "C" annotation depends on your compiler defaults, we cannot use it. Instead, this macro selects the default for your target platform.

Ideally, the macro would expand to extern "<abi>" so you would be able to write:

// THIS DOES NOT WORK!
pub fn eficall!{} foobar() {
    // ...
}

However, macros are evaluated too late for this to work. Instead, the entire construct must be wrapped in a macro, which then expands to the same construct but with extern "<abi>" inserted at the correct place:

use r_efi::{eficall, eficall_abi};

eficall!{pub fn foobar() {
    // ...
}}

type FooBar = eficall!{fn(u8) -> (u8)};

The eficall!{} macro takes either a function-type or function-definition as argument. It inserts extern "<abi>" after the function qualifiers, but before the fn keyword.

§Internals

The eficall!{} macro tries to parse the function header so it can insert extern "<abi>" at the right place. If, for whatever reason, this does not work with a particular syntax, you can use the internal eficall_abi!{} macro. This macro takes two token-streams as input and evaluates to the concatenation of both token-streams, but separated by the selected ABI.

For instance, the following 3 type definitions are equivalent, assuming the selected ABI is “C”:

use r_efi::{eficall, eficall_abi};

type FooBar1 = unsafe extern "C" fn(u8) -> (u8);
type FooBar2 = eficall!{unsafe fn(u8) -> (u8)};
type FooBar3 = eficall_abi!{(unsafe), (fn(u8) -> (u8))};

§Calling Conventions

The UEFI specification defines the calling convention for each platform individually. It usually refers to other standards for details, but adds some restrictions on top. As of this writing, it mentions:

  • aarch32 / arm: The aapcs calling-convention is used. It is native to aarch32 and described in a document called “Procedure Call Standard for the ARM Architecture”. It is openly distributed by ARM and widely known under the keyword aapcs.
  • aarch64: The aapcs64 calling-convention is used. It is native to aarch64 and described in a document called “Procedure Call Standard for the ARM 64-bit Architecture (AArch64)”. It is openly distributed by ARM and widely known under the keyword aapcs64.
  • ia-64: The “P64 C Calling Convention” as described in the “Itanium Software Conventions and Runtime Architecture Guide”. It is also standardized in the “Intel Itanium SAL Specification”.
  • RISC-V: The “Standard RISC-V C Calling Convention” is used. The UEFI specification describes it in detail, but also refers to the official RISC-V resources for detailed information.
  • x86 / ia-32: The cdecl C calling convention is used. Originated in the C Language and originally tightly coupled to C specifics. Unclear whether a formal specification exists (does anyone know?). Most compilers support it under the cdecl keyword, and in nearly all situations it is the default on x86.
  • x86_64 / amd64 / x64: The win64 calling-convention is used. It is similar to the sysv64 convention that is used on most non-windows x86_64 systems, but not exactly the same. Microsoft provides open documentation on it. See MSDN “x64 Software Conventions -> Calling Conventions”. The UEFI Specification does not directly refer to win64, but contains a full specification of the calling convention itself.

Note that in most cases the UEFI Specification adds several more restrictions on top of the common calling-conventions. These restrictions usually do not affect how the compiler will lay out the function calls. Instead, it usually only restricts the set of APIs that are allowed in UEFI. Therefore, most compilers already support the calling conventions used on UEFI.

§Variadics

For some reason, the rust compiler allows variadics only in combination with the "C" calling convention, even if the selected calling-convention matches what "C" would select on the target platform. Hence, you will very likely be unable to use variadics with this macro. Luckily, all of the UEFI functions that use variadics are wrappers around more low-level accessors, so they are not necessarily required.