From 3d2c7af7b0a034ef8902ff3887cd5c223b92d232 Mon Sep 17 00:00:00 2001 From: Nikolai Vazquez Date: Fri, 17 Nov 2023 18:07:47 +0100 Subject: [PATCH] Always use pre-`main` instead of `linkme` This approach is more portable and reliable. --- CHANGELOG.md | 6 ++ Cargo.toml | 5 -- macros/src/attr_options.rs | 4 -- macros/src/lib.rs | 131 ++++++++++++++----------------------- src/divan.rs | 2 +- src/entry/list.rs | 4 -- src/entry/mod.rs | 21 ------ src/private.rs | 3 - tests/attr_options.rs | 2 +- tests/entry_properties.rs | 2 +- tests/weird_usage.rs | 2 +- 11 files changed, 60 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a33bef4..015dc0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Versioning](http://semver.org/spec/v2.0.0.html). ### Added +- Support for NetBSD, DragonFly BSD, and Haiku OS by using pre-`main`. + - Set global thread counts using: - [`Divan::threads`](https://docs.rs/divan/X.Y.Z/divan/struct.Divan.html#method.threads) - `--threads A B C...` CLI arg @@ -62,6 +64,10 @@ Versioning](http://semver.org/spec/v2.0.0.html). - Now calling [`black_box`] at the end of the benchmark loop when deferring use of inputs or [`Drop`] of outputs. +- Use pre-`main` to register benchmarks and benchmark groups instead of + [`linkme`](https://docs.rs/linkme). This is generally be more portable and + reliable. + ## [0.1.2] - 2023-10-28 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index 2fc7957..abe8492 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,11 +19,6 @@ clap = { version = "4", default-features = false, features = ["std", "env"] } condtype = "1.3" regex = { package = "regex-lite", version = "0.1", default-features = false, features = ["std", "string"] } -[target.'cfg(not(any(windows, target_os = "linux", target_os = "android")))'.dependencies] -# We use linkme to make benchmark/group entries discoverable. On platforms where -# it doesn't work, we instead use pre-main to build a linked list. -linkme = { version = "0.3", default-features = false } - [features] default = ["wrap_help"] help = ["clap/help"] diff --git a/macros/src/attr_options.rs b/macros/src/attr_options.rs index c1bb326..2d7049c 100644 --- a/macros/src/attr_options.rs +++ b/macros/src/attr_options.rs @@ -24,9 +24,6 @@ pub(crate) struct AttrOptions { /// reference crate `x` instead. pub std_crate: proc_macro2::TokenStream, - /// `divan::__private::linkme`. - pub linkme_crate: proc_macro2::TokenStream, - /// Custom name for the benchmark or group. pub name_expr: Option, @@ -162,7 +159,6 @@ impl AttrOptions { Ok(Self { std_crate: quote! { #private_mod::std }, - linkme_crate: quote! { #private_mod::linkme }, private_mod, name_expr, generic, diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 5e15bb9..e79476c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -26,6 +26,40 @@ impl Macro<'_> { } } +/// Attributes applied to a `static` containing a pointer to a function to run +/// before `main`. +fn pre_main_attrs() -> proc_macro2::TokenStream { + quote! { + #[used] + #[cfg_attr( + // ELF + any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "fuchsia", + target_os = "haiku", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd", + ), + link_section = ".init_array", + )] + #[cfg_attr( + // Mach-O + any( + target_os = "ios", + target_os = "macos", + target_os = "tvos", + target_os = "watchos", + ), + link_section = "__DATA,__mod_init_func,mod_init_funcs", + )] + #[cfg_attr(windows, link_section = ".CRT$XCU")] + } +} + #[proc_macro_attribute] pub fn bench(options: TokenStream, item: TokenStream) -> TokenStream { let fn_item = item.clone(); @@ -38,7 +72,7 @@ pub fn bench(options: TokenStream, item: TokenStream) -> TokenStream { }; // Items needed by generated code. - let AttrOptions { private_mod, linkme_crate, .. } = &options; + let AttrOptions { private_mod, .. } = &options; let fn_ident = &fn_sig.ident; let fn_name = fn_ident.to_string(); @@ -112,6 +146,8 @@ pub fn bench(options: TokenStream, item: TokenStream) -> TokenStream { let option_none = quote! { #private_mod::None }; let option_some = quote! { #private_mod::Some }; + let pre_main_attrs = pre_main_attrs(); + // Creates a `GroupEntry` static for generic benchmarks. let make_generic_group = |generic_benches: proc_macro2::TokenStream| { let entry = quote! { @@ -122,33 +158,11 @@ pub fn bench(options: TokenStream, item: TokenStream) -> TokenStream { }; quote! { - // Use a distributed slice via `linkme` by default. - #[cfg(not(any( - windows, - target_os = "linux", - target_os = "android", - )))] - #[#linkme_crate::distributed_slice(#private_mod::GROUP_ENTRIES)] - #[linkme(crate = #linkme_crate)] - #[doc(hidden)] - static #static_ident: #private_mod::GroupEntry = #entry; - - // On other platforms we push this static into `GROUP_ENTRIES` - // before `main` is called. - #[cfg(any( - windows, - target_os = "linux", - target_os = "android", - ))] + // Push this static into `GROUP_ENTRIES` before `main` is called. static #static_ident: #private_mod::GroupEntry = { { // Add `push` to the initializer section. - #[used] - #[cfg_attr( - any(target_os = "linux", target_os = "android"), - link_section = ".init_array", - )] - #[cfg_attr(windows, link_section = ".CRT$XCU")] + #pre_main_attrs static PUSH: extern "C" fn() = push; extern "C" fn push() { @@ -225,33 +239,12 @@ pub fn bench(options: TokenStream, item: TokenStream) -> TokenStream { }; quote! { - // Use a distributed slice via `linkme` by default. - #[cfg(not(any( - windows, - target_os = "linux", - target_os = "android", - )))] - #[#linkme_crate::distributed_slice(#private_mod::BENCH_ENTRIES)] - #[linkme(crate = #linkme_crate)] - #[doc(hidden)] - static #static_ident: #private_mod::BenchEntry = #entry; - - // On other platforms we push this static into - // `BENCH_ENTRIES` before `main` is called. - #[cfg(any( - windows, - target_os = "linux", - target_os = "android", - ))] + // Push this static into `BENCH_ENTRIES` before `main` is + // called. static #static_ident: #private_mod::BenchEntry = { { // Add `push` to the initializer section. - #[used] - #[cfg_attr( - any(target_os = "linux", target_os = "android"), - link_section = ".init_array", - )] - #[cfg_attr(windows, link_section = ".CRT$XCU")] + #pre_main_attrs static PUSH: extern "C" fn() = push; extern "C" fn push() { @@ -365,7 +358,7 @@ pub fn bench_group(options: TokenStream, item: TokenStream) -> TokenStream { }; // Items needed by generated code. - let AttrOptions { private_mod, linkme_crate, .. } = &options; + let AttrOptions { private_mod, .. } = &options; // TODO: Make module parsing cheaper by parsing only the necessary parts. let mod_item = item.clone(); @@ -393,41 +386,14 @@ pub fn bench_group(options: TokenStream, item: TokenStream) -> TokenStream { let meta = entry_meta_expr(&mod_name, &options, ignore_attr_ident); - let entry_static = quote! { - static #static_ident: #private_mod::GroupEntry = #private_mod::GroupEntry { - meta: #meta, - generic_benches: #private_mod::None, - }; - }; + let pre_main_attrs = pre_main_attrs(); let generated_items = quote! { - // Use a distributed slice via `linkme` by default. - #[cfg(not(any( - windows, - target_os = "linux", - target_os = "android", - )))] - #[#linkme_crate::distributed_slice(#private_mod::GROUP_ENTRIES)] - #[linkme(crate = #linkme_crate)] - #[doc(hidden)] - #entry_static - - // On other platforms we push this static into `GROUP_ENTRIES` before - // `main` is called. - #[cfg(any( - windows, - target_os = "linux", - target_os = "android", - ))] + // Push this static into `GROUP_ENTRIES` before `main` is called. static #static_ident: #private_mod::EntryList<#private_mod::GroupEntry> = { { // Add `push` to the initializer section. - #[used] - #[cfg_attr( - any(target_os = "linux", target_os = "android"), - link_section = ".init_array", - )] - #[cfg_attr(windows, link_section = ".CRT$XCU")] + #pre_main_attrs static PUSH: extern "C" fn() = push; extern "C" fn push() { @@ -436,7 +402,10 @@ pub fn bench_group(options: TokenStream, item: TokenStream) -> TokenStream { } #private_mod::EntryList::new({ - #entry_static + static #static_ident: #private_mod::GroupEntry = #private_mod::GroupEntry { + meta: #meta, + generic_benches: #private_mod::None, + }; &#static_ident }) diff --git a/src/divan.rs b/src/divan.rs index 6ef186d..ea2f998 100644 --- a/src/divan.rs +++ b/src/divan.rs @@ -97,7 +97,7 @@ impl Divan { pub(crate) fn run_action(&self, action: Action) { let mut tree: Vec = if cfg!(miri) { - // Miri does not work with `linkme`. + // Miri does not work with our linker tricks. Vec::new() } else { let group_entries = &crate::entry::GROUP_ENTRIES; diff --git a/src/entry/list.rs b/src/entry/list.rs index 04e7cdd..5ad06bd 100644 --- a/src/entry/list.rs +++ b/src/entry/list.rs @@ -13,7 +13,6 @@ pub struct EntryList { } impl EntryList { - #[cfg(any(windows, target_os = "linux", target_os = "android"))] pub(crate) const fn root() -> Self { Self { entry: None, next: AtomicPtr::new(ptr::null_mut()) } } @@ -36,9 +35,6 @@ impl EntryList { } /// Creates an iterator over entries in `self`. - /// - /// We use `.iter()` to be a drop-in replacement for - /// `linkme::DistributedSlice`. #[inline] pub fn iter(&self) -> impl Iterator { let mut list = Some(self); diff --git a/src/entry/mod.rs b/src/entry/mod.rs index f85a22e..ef4f424 100644 --- a/src/entry/mod.rs +++ b/src/entry/mod.rs @@ -18,32 +18,11 @@ pub(crate) use tree::EntryTree; /// /// Note: generic-type benchmark entries are instead stored in `GROUP_ENTRIES` /// in `generic_benches`. -#[cfg(any(windows, target_os = "linux", target_os = "android"))] pub static BENCH_ENTRIES: EntryList = EntryList::root(); -/// Benchmark entries generated by `#[divan::bench]`. -/// -/// Note: generic-type benchmark entries are instead stored in `GROUP_ENTRIES` -/// in `generic_benches`. -#[cfg(not(any(windows, target_os = "linux", target_os = "android")))] -#[cfg_attr( - not(any(windows, target_os = "linux", target_os = "android")), - linkme::distributed_slice -)] -pub static BENCH_ENTRIES: [BenchEntry] = [..]; - /// Group entries generated by `#[divan::bench_group]`. -#[cfg(any(windows, target_os = "linux", target_os = "android"))] pub static GROUP_ENTRIES: EntryList = EntryList::root(); -/// Group entries generated by `#[divan::bench_group]`. -#[cfg(not(any(windows, target_os = "linux", target_os = "android")))] -#[cfg_attr( - not(any(windows, target_os = "linux", target_os = "android")), - linkme::distributed_slice -)] -pub static GROUP_ENTRIES: [GroupEntry] = [..]; - /// Compile-time entry for a benchmark, generated by `#[divan::bench]`. pub struct BenchEntry { /// Entry metadata. diff --git a/src/private.rs b/src/private.rs index 66ec0e4..e567b6c 100644 --- a/src/private.rs +++ b/src/private.rs @@ -3,9 +3,6 @@ pub use std::{ self, any, borrow::Cow, default::Default, iter::FromIterator, option::Option::*, sync::OnceLock, }; -#[cfg(not(any(windows, target_os = "linux", target_os = "android")))] -pub use linkme; - pub use crate::{ bench::BenchOptions, entry::{ diff --git a/tests/attr_options.rs b/tests/attr_options.rs index 61be5ea..2096b97 100644 --- a/tests/attr_options.rs +++ b/tests/attr_options.rs @@ -1,6 +1,6 @@ // Tests that attribute options produce the correct results. -// Miri does not work with `linkme`. +// Miri cannot discover benchmarks. #![cfg(not(miri))] use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; diff --git a/tests/entry_properties.rs b/tests/entry_properties.rs index 65cd1fc..9775eb5 100644 --- a/tests/entry_properties.rs +++ b/tests/entry_properties.rs @@ -1,6 +1,6 @@ // Tests that entry benchmarks/groups have correct generated properties. -// Miri does not work with `linkme`. +// Miri cannot discover benchmarks. #![cfg(not(miri))] use divan::__private::{EntryMeta, BENCH_ENTRIES, GROUP_ENTRIES}; diff --git a/tests/weird_usage.rs b/tests/weird_usage.rs index 4a42f46..423a506 100644 --- a/tests/weird_usage.rs +++ b/tests/weird_usage.rs @@ -1,6 +1,6 @@ // Tests that ensure weird (but valid) usage behave as expected. -// Miri does not work with `linkme`. +// Miri cannot discover benchmarks. #![cfg(not(miri))] use std::time::Duration;