Skip to content

Commit

Permalink
Always use pre-main instead of linkme
Browse files Browse the repository at this point in the history
This approach is more portable and reliable.
  • Loading branch information
nvzqz committed Nov 17, 2023
1 parent d60ad58 commit b27892a
Show file tree
Hide file tree
Showing 11 changed files with 60 additions and 122 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -57,6 +59,10 @@ Versioning](http://semver.org/spec/v2.0.0.html).
- [`black_box`] inside benchmark loop when deferring [`Drop`] of outputs. This
is now done after the loop.

- [`linkme`](https://docs.rs/linkme) dependency in favor of pre-`main` to
register benchmarks and benchmark groups. This is generally be more portable
and reliable.

### Changed

- Now calling [`black_box`] at the end of the benchmark loop when deferring use
Expand Down
5 changes: 0 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
4 changes: 0 additions & 4 deletions macros/src/attr_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Expr>,

Expand Down Expand Up @@ -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,
Expand Down
131 changes: 50 additions & 81 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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! {
Expand All @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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() {
Expand All @@ -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
})
Expand Down
2 changes: 1 addition & 1 deletion src/divan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ impl Divan {

pub(crate) fn run_action(&self, action: Action) {
let mut tree: Vec<EntryTree> = 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;
Expand Down
4 changes: 0 additions & 4 deletions src/entry/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ pub struct EntryList<T: 'static> {
}

impl<T> EntryList<T> {
#[cfg(any(windows, target_os = "linux", target_os = "android"))]
pub(crate) const fn root() -> Self {
Self { entry: None, next: AtomicPtr::new(ptr::null_mut()) }
}
Expand All @@ -36,9 +35,6 @@ impl<T> EntryList<T> {
}

/// 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<Item = &T> {
let mut list = Some(self);
Expand Down
21 changes: 0 additions & 21 deletions src/entry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BenchEntry> = 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<GroupEntry> = 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.
Expand Down
3 changes: 0 additions & 3 deletions src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down
2 changes: 1 addition & 1 deletion tests/attr_options.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down
2 changes: 1 addition & 1 deletion tests/entry_properties.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down
2 changes: 1 addition & 1 deletion tests/weird_usage.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down

0 comments on commit b27892a

Please sign in to comment.