portable_link_section 0.1.0

A portable version of the #[link_section] macro.
Documentation
mod attr;
mod error;

use self::attr::LinkSegment;
use self::error::Error;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, quote};

/// A portable version of Rust's [`#[link_section]`][ls] macro.
///
/// In essence the [`#[portable_link_section]`][pls] macro is a restricted version of [`#[link_section]`][ls].
///
/// The [`#[portable_link_section]`][pls] macro accepts one of the following `segment` identifiers:
///
/// - `text`: code
/// - `data`: writable data
/// - `rodata`: read-only data
/// - `bss`: zero-initialized data
///
/// When given `text` or `data` the macro expects another `group` identifier that
/// has to follow some strict rules to provide portability.
///
/// # Usage
///
/// ```rust
/// use portable_link_section::portable_link_section;
///
/// #[portable_link_section(text(my_group))]
/// fn func_0() {}
///
/// #[portable_link_section(text(hot_interp))]
/// fn func_1() {}
///
/// #[portable_link_section(text(cold_interp))]
/// fn func_2() {}
///
/// #[portable_link_section(data(my_group))]
/// static DATA_0: i32 = 42;
///
/// #[portable_link_section(rodata)]
/// static DATA_1: i32 = 42;
///
/// #[portable_link_section(bss)]
/// static DATA_2: i32 = 0;
/// ```
///
/// # Portability
///
/// The [`#[portable_link_section]`][pls] macro supports the following binary file formats:
///
/// - [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format)
/// - [Mach-O](https://en.wikipedia.org/wiki/Mach-O)
/// - [COFF](https://en.wikipedia.org/wiki/COFF)
///
/// [ls]: https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute
/// [pls]: [`crate::portable_link_section`]
#[proc_macro_attribute]
pub fn portable_link_section(attr: TokenStream, item: TokenStream) -> TokenStream {
    match portable_link_section_impl(attr.into(), item.into()) {
        Ok(tt) => tt.into_token_stream().into(),
        Err(error) => error.into_token_stream().into(),
    }
}

fn portable_link_section_impl(
    attr: TokenStream2,
    item: TokenStream2,
) -> Result<TokenStream2, Error> {
    let attrs = attr.into_iter();
    let params = LinkSegment::try_from(attrs)?;
    let elf_segment = Elf(&params);
    let macho_segment = Macho(&params);
    let coff_segment = Coff(&params);
    let elf_link_section = quote! {
        #[cfg_attr(
            all(target_family = "unix", not(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))),
            unsafe(link_section = #elf_segment),
        )]
    };
    let macho_link_section = quote! {
        #[cfg_attr(
            any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"),
            unsafe(link_section = #macho_segment),
        )]
    };
    let coff_link_section = quote! {
        #[cfg_attr(
            target_os = "windows",
            unsafe(link_section = #coff_segment),
        )]
    };
    Ok(quote! {
        #elf_link_section
        #macho_link_section
        #coff_link_section
        #item
    })
}

struct Elf<'a, T>(&'a T);
struct Macho<'a, T>(&'a T);
struct Coff<'a, T>(&'a T);

impl ToTokens for Elf<'_, LinkSegment> {
    fn to_tokens(&self, tokens: &mut TokenStream2) {
        let toks = match self.0 {
            LinkSegment::Text(group) => {
                quote!(concat!(".text.", stringify!(#group)))
            }
            LinkSegment::Data(group) => {
                quote!(concat!(".data.", stringify!(#group)))
            }
            LinkSegment::Rodata => quote! { ".rodata" },
            LinkSegment::Bss => quote! { ".bss" },
        };
        tokens.extend(toks);
    }
}

impl ToTokens for Macho<'_, LinkSegment> {
    fn to_tokens(&self, tokens: &mut TokenStream2) {
        let toks = match self.0 {
            LinkSegment::Text(group) => {
                quote!(concat!("__TEXT,__", stringify!(#group)))
            }
            LinkSegment::Data(group) => {
                quote!(concat!("__DATA,__", stringify!(#group)))
            }
            LinkSegment::Rodata => quote! { "__TEXT,__const" },
            LinkSegment::Bss => quote! { "__DATA,__bss" },
        };
        tokens.extend(toks);
    }
}

impl ToTokens for Coff<'_, LinkSegment> {
    fn to_tokens(&self, tokens: &mut TokenStream2) {
        let toks = match self.0 {
            LinkSegment::Text(group) => {
                quote!(concat!(".text$", stringify!(#group)))
            }
            LinkSegment::Data(group) => {
                quote!(concat!(".data$", stringify!(#group)))
            }
            LinkSegment::Rodata => quote! { ".rdata" },
            LinkSegment::Bss => quote! { ".bss" },
        };
        tokens.extend(toks);
    }
}