blob: a799065a26a1626cc9332399d9b809d38b74f05d [file] [log] [blame]
use core::fmt::{self, Debug};
use core::hint;
use core::mem;
use core::ops::Deref;
use core::slice;
use crate::__private::Slice;
/// Collection of static elements that are gathered into a contiguous section of
/// the binary by the linker.
///
/// The implementation is based on `link_section` attributes and
/// platform-specific linker support. It does not involve life-before-main or
/// any other runtime initialization on any platform. This is a zero-cost safe
/// abstraction that operates entirely during compilation and linking.
///
/// ## Declaration
///
/// A static distributed slice may be declared by writing `#[distributed_slice]`
/// on a static item whose type is `[T]` for some type `T`.
///
/// ```
/// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))]
/// #
/// # struct Bencher;
/// #
/// use linkme::distributed_slice;
///
/// #[distributed_slice]
/// pub static BENCHMARKS: [fn(&mut Bencher)];
/// ```
///
/// The attribute rewrites the `[T]` type of the static into
/// `DistributedSlice<[T]>`, so the static in the example technically has type
/// `DistributedSlice<[fn(&mut Bencher)]>`.
///
/// ## Elements
///
/// Slice elements may be registered into a distributed slice by a
/// `#[distributed_slice(...)]` attribute in which the path to the distributed
/// slice is given in the parentheses. The initializer is required to be a const
/// expression.
///
/// Elements may be defined in the same crate that declares the distributed
/// slice, or in any downstream crate. Elements across all crates linked into
/// the final binary will be observed to be present in the slice at runtime.
///
/// ```
/// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))]
/// #
/// # mod other_crate {
/// # use linkme::distributed_slice;
/// #
/// # pub struct Bencher;
/// #
/// # #[distributed_slice]
/// # pub static BENCHMARKS: [fn(&mut Bencher)];
/// # }
/// #
/// # use other_crate::Bencher;
/// #
/// use linkme::distributed_slice;
/// use other_crate::BENCHMARKS;
///
/// #[distributed_slice(BENCHMARKS)]
/// static BENCH_DESERIALIZE: fn(&mut Bencher) = bench_deserialize;
///
/// fn bench_deserialize(b: &mut Bencher) {
/// /* ... */
/// }
/// ```
///
/// The compiler will require that the static element type matches with the
/// element type of the distributed slice. If the two do not match, the program
/// will not compile.
///
/// ```compile_fail
/// # mod other_crate {
/// # use linkme::distributed_slice;
/// #
/// # pub struct Bencher;
/// #
/// # #[distributed_slice]
/// # pub static BENCHMARKS: [fn(&mut Bencher)];
/// # }
/// #
/// # use linkme::distributed_slice;
/// # use other_crate::BENCHMARKS;
/// #
/// #[distributed_slice(BENCHMARKS)]
/// static BENCH_WTF: usize = 999;
/// ```
///
/// ```text
/// error[E0308]: mismatched types
/// --> src/distributed_slice.rs:65:19
/// |
/// 17 | static BENCH_WTF: usize = 999;
/// | ^^^^^ expected fn pointer, found `usize`
/// |
/// = note: expected fn pointer `fn(&mut other_crate::Bencher)`
/// found type `usize`
/// ```
///
/// ## Function elements
///
/// As a shorthand for the common case of distributed slices containing function
/// pointers, the distributed\_slice attribute may be applied directly to a
/// function definition to place a pointer to that function into a distributed
/// slice.
///
/// ```
/// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))]
/// #
/// # pub struct Bencher;
/// #
/// use linkme::distributed_slice;
///
/// #[distributed_slice]
/// pub static BENCHMARKS: [fn(&mut Bencher)];
///
/// // Equivalent to:
/// //
/// // #[distributed_slice(BENCHMARKS)]
/// // static _: fn(&mut Bencher) = bench_deserialize;
/// //
/// #[distributed_slice(BENCHMARKS)]
/// fn bench_deserialize(b: &mut Bencher) {
/// /* ... */
/// }
/// ```
pub struct DistributedSlice<T: ?Sized + Slice> {
name: &'static str,
section_start: StaticPtr<T::Element>,
section_stop: StaticPtr<T::Element>,
dupcheck_start: StaticPtr<usize>,
dupcheck_stop: StaticPtr<usize>,
}
struct StaticPtr<T> {
ptr: *const T,
}
unsafe impl<T> Send for StaticPtr<T> {}
unsafe impl<T> Sync for StaticPtr<T> {}
impl<T> Copy for StaticPtr<T> {}
impl<T> Clone for StaticPtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> DistributedSlice<[T]> {
#[doc(hidden)]
#[cfg(any(
target_os = "none",
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "tvos",
target_os = "android",
target_os = "fuchsia",
target_os = "illumos",
target_os = "freebsd",
target_os = "openbsd",
target_os = "psp",
))]
pub const unsafe fn private_new(
name: &'static str,
section_start: *const T,
section_stop: *const T,
dupcheck_start: *const usize,
dupcheck_stop: *const usize,
) -> Self {
DistributedSlice {
name,
section_start: StaticPtr { ptr: section_start },
section_stop: StaticPtr { ptr: section_stop },
dupcheck_start: StaticPtr {
ptr: dupcheck_start,
},
dupcheck_stop: StaticPtr { ptr: dupcheck_stop },
}
}
#[doc(hidden)]
#[cfg(any(target_os = "uefi", target_os = "windows"))]
pub const unsafe fn private_new(
name: &'static str,
section_start: *const [T; 0],
section_stop: *const [T; 0],
dupcheck_start: *const (),
dupcheck_stop: *const (),
) -> Self {
DistributedSlice {
name,
section_start: StaticPtr {
ptr: section_start as *const T,
},
section_stop: StaticPtr {
ptr: section_stop as *const T,
},
dupcheck_start: StaticPtr {
ptr: dupcheck_start as *const usize,
},
dupcheck_stop: StaticPtr {
ptr: dupcheck_stop as *const usize,
},
}
}
#[doc(hidden)]
#[inline]
pub unsafe fn private_typecheck(self, get: fn() -> &'static T) {
let _ = get;
}
/// Retrieve a contiguous slice containing all the elements linked into this
/// program.
///
/// **Note**: Ordinarily this method should not need to be called because
/// `DistributedSlice<[T]>` already behaves like `&'static [T]` in most ways
/// through the power of `Deref`. In particular, iteration and indexing and
/// method calls can all happen directly on the static without calling
/// `static_slice()`.
///
/// ```no_run
/// # #![cfg_attr(feature = "used_linker", feature(used_with_arg))]
/// #
/// # struct Bencher;
/// #
/// use linkme::distributed_slice;
///
/// #[distributed_slice]
/// static BENCHMARKS: [fn(&mut Bencher)];
///
/// fn main() {
/// // Iterate the elements.
/// for bench in BENCHMARKS {
/// /* ... */
/// }
///
/// // Index into the elements.
/// let first = BENCHMARKS[0];
///
/// // Slice the elements.
/// let except_first = &BENCHMARKS[1..];
///
/// // Invoke methods on the underlying slice.
/// let len = BENCHMARKS.len();
/// }
/// ```
pub fn static_slice(self) -> &'static [T] {
if self.dupcheck_start.ptr.wrapping_add(1) < self.dupcheck_stop.ptr {
panic!("duplicate #[distributed_slice] with name \"{}\"", self.name);
}
let stride = mem::size_of::<T>();
let start = self.section_start.ptr;
let stop = self.section_stop.ptr;
let byte_offset = stop as usize - start as usize;
let len = match byte_offset.checked_div(stride) {
Some(len) => len,
// The #[distributed_slice] call checks `size_of::<T>() > 0` before
// using the unsafe `private_new`.
None => unsafe { hint::unreachable_unchecked() },
};
// On Windows, the implementation involves growing a &[T; 0] to
// encompass elements that we have asked the linker to place immediately
// after that location. The compiler sees this as going "out of bounds"
// based on provenance, so we must conceal what is going on.
#[cfg(any(target_os = "uefi", target_os = "windows"))]
let start = hint::black_box(start);
unsafe { slice::from_raw_parts(start, len) }
}
}
impl<T> Copy for DistributedSlice<[T]> {}
impl<T> Clone for DistributedSlice<[T]> {
fn clone(&self) -> Self {
*self
}
}
impl<T: 'static> Deref for DistributedSlice<[T]> {
type Target = [T];
fn deref(&self) -> &'static Self::Target {
self.static_slice()
}
}
impl<T: 'static> IntoIterator for DistributedSlice<[T]> {
type Item = &'static T;
type IntoIter = slice::Iter<'static, T>;
fn into_iter(self) -> Self::IntoIter {
self.static_slice().iter()
}
}
impl<T> Debug for DistributedSlice<[T]>
where
T: Debug + 'static,
{
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
Debug::fmt(self.static_slice(), formatter)
}
}