Skip to content

Commit

Permalink
Use std crate via ::std instead of private mod
Browse files Browse the repository at this point in the history
This change tries to optimize for simplicity and compile time.

It's extremely unlikely that users do `extern crate std as x`, so we can
simplify this code to not rely on `private_mod`.

This is motivated by simplifying code for future additions.
  • Loading branch information
nvzqz committed Nov 1, 2024
1 parent a2bcced commit 646419c
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 42 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ Versioning](http://semver.org/spec/v2.0.0.html).

- Move measured loop overhead from `SharedContext` to global `OnceLock`.

- Macros no longer rely on `std` being re-exported by Divan. Instead they use
`::std` or `::core` to greatly simplify code. Although this is technically a
breaking change, it is extremely unlikely to do `extern crate std as x`.

## [0.1.14] - 2024-02-17

### Fixed
Expand Down
27 changes: 10 additions & 17 deletions macros/src/attr_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use syn::{
Expr, ExprArray, Ident, Token, Type,
};

use crate::Macro;
use crate::{tokens, Macro};

/// Values from parsed options shared between `#[divan::bench]` and
/// `#[divan::bench_group]`.
Expand All @@ -17,13 +17,6 @@ pub(crate) struct AttrOptions {
/// `divan::__private`.
pub private_mod: proc_macro2::TokenStream,

/// `divan::__private::std`.
///
/// Access to libstd is through a re-export because it's possible (although
/// unlikely) to do `extern crate x as std`, which would cause `::std` to
/// reference crate `x` instead.
pub std_crate: proc_macro2::TokenStream,

/// Custom name for the benchmark or group.
pub name_expr: Option<Expr>,

Expand Down Expand Up @@ -203,7 +196,6 @@ impl AttrOptions {

let divan_crate = divan_crate.unwrap_or_else(|| syn::parse_quote!(::divan));
let private_mod = quote! { #divan_crate::__private };
let std_crate = quote! { #private_mod::std };

let counters = counters.iter().map(|(expr, type_name)| match type_name {
Some(type_name) => {
Expand All @@ -212,7 +204,7 @@ impl AttrOptions {
// We do a scoped import for the expression to override any
// local `From` trait.
{
use #std_crate::convert::From as _;
use ::std::convert::From as _;

#divan_crate::counter::#type_name::from(#expr)
}
Expand All @@ -229,7 +221,7 @@ impl AttrOptions {
})
.unwrap_or_default();

Ok(Self { std_crate, private_mod, name_expr, args_expr, generic, counters, bench_options })
Ok(Self { private_mod, name_expr, args_expr, generic, counters, bench_options })
}

/// Produces a function expression for creating `BenchOptions`.
Expand All @@ -250,6 +242,7 @@ impl AttrOptions {
}

let private_mod = &self.private_mod;
let option_some = tokens::option_some();

// Directly set fields on `BenchOptions`. This simplifies things by:
// - Having a single source of truth
Expand All @@ -261,7 +254,7 @@ impl AttrOptions {
// with docs and type info.
if self.bench_options.is_empty() && self.counters.is_empty() && ignore_attr_ident.is_none()
{
quote! { #private_mod::None }
tokens::option_none()
} else {
let options_iter = self.bench_options.iter().map(|(option, value)| {
let option_name = option.to_string();
Expand All @@ -275,7 +268,7 @@ impl AttrOptions {
"threads" => {
wrapped_value = if is_lit_array(value) {
// If array of literals, just use `&[...]`.
quote! { #private_mod::Cow::Borrowed(&#value) }
quote! { ::std::borrow::Cow::Borrowed(&#value) }
} else {
quote! { #private_mod::IntoThreads::into_threads(#value) }
};
Expand All @@ -294,18 +287,18 @@ impl AttrOptions {
_ => value,
};

quote! { #option: #private_mod::Some(#value), }
quote! { #option: #option_some(#value), }
});

let ignore = match ignore_attr_ident {
Some(ignore_attr_ident) => quote! { #ignore_attr_ident: #private_mod::Some(true), },
Some(ignore_attr_ident) => quote! { #ignore_attr_ident: #option_some(true), },
None => Default::default(),
};

let counters = &self.counters;

quote! {
#private_mod::Some(|| {
#option_some(|| {
#[allow(clippy::needless_update)]
#private_mod::BenchOptions {
#(#options_iter)*
Expand All @@ -316,7 +309,7 @@ impl AttrOptions {

#counters

..#private_mod::Default::default()
..::std::default::Default::default()
}
})
}
Expand Down
44 changes: 22 additions & 22 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use proc_macro::TokenStream;
use quote::{quote, ToTokens};

mod attr_options;
mod tokens;

use attr_options::*;
use syn::{Expr, FnArg};
Expand Down Expand Up @@ -68,23 +69,23 @@ fn pre_main_attrs() -> proc_macro2::TokenStream {
}
}

fn unsupported_error(
std_crate: &proc_macro2::TokenStream,
attr_name: &str,
) -> proc_macro2::TokenStream {
fn unsupported_error(attr_name: &str) -> proc_macro2::TokenStream {
let elf = systems::elf();
let mach_o = systems::mach_o();

let error = format!("Unsupported target OS for `#[divan::{attr_name}]`");

quote! {
#[cfg(not(any(windows, #elf, #mach_o)))]
#std_crate::compile_error!(#error);
::std::compile_error!(#error);
}
}

#[proc_macro_attribute]
pub fn bench(options: TokenStream, item: TokenStream) -> TokenStream {
let option_none = tokens::option_none();
let option_some = tokens::option_some();

let fn_item = item.clone();
let fn_item = syn::parse_macro_input!(fn_item as syn::ItemFn);
let fn_sig = &fn_item.sig;
Expand All @@ -98,7 +99,7 @@ pub fn bench(options: TokenStream, item: TokenStream) -> TokenStream {
};

// Items needed by generated code.
let AttrOptions { private_mod, std_crate, .. } = &options;
let AttrOptions { private_mod, .. } = &options;

let fn_ident = &fn_sig.ident;
let fn_name = fn_ident.to_string();
Expand Down Expand Up @@ -271,9 +272,9 @@ pub fn bench(options: TokenStream, item: TokenStream) -> TokenStream {

// Ensure `args` is set if arguments are provided after `Bencher`.
(_, None) => quote! {
#std_crate::compile_error!(#std_crate::concat!(
::std::compile_error!(::std::concat!(
"expected 'args' option containing '",
#std_crate::stringify!(#last_arg_type_tokens),
::std::stringify!(#last_arg_type_tokens),
"'",
))
},
Expand All @@ -284,11 +285,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();
let unsupported_error = unsupported_error(std_crate, attr_name);
let unsupported_error = unsupported_error(attr_name);

// Creates a `GroupEntry` static for generic benchmarks.
let make_generic_group = |generic_benches: proc_macro2::TokenStream| {
Expand Down Expand Up @@ -478,7 +476,7 @@ pub fn bench(options: TokenStream, item: TokenStream) -> TokenStream {
quote! {
static __DIVAN_GENERIC_BENCHES: [#private_mod::GenericBenchEntry; __DIVAN_CONST_COUNT]
= match #private_mod::shrink_array([#(#generic_benches),*]) {
#private_mod::Some(array) => array,
Some(array) => array,
_ => panic!("external 'consts' cannot contain more than 20 values"),
};

Expand Down Expand Up @@ -512,7 +510,9 @@ pub fn bench_group(options: TokenStream, item: TokenStream) -> TokenStream {
};

// Items needed by generated code.
let AttrOptions { private_mod, std_crate, .. } = &options;
let AttrOptions { private_mod, .. } = &options;

let option_none = tokens::option_none();

// TODO: Make module parsing cheaper by parsing only the necessary parts.
let mod_item = item.clone();
Expand Down Expand Up @@ -541,7 +541,7 @@ pub fn bench_group(options: TokenStream, item: TokenStream) -> TokenStream {
let meta = entry_meta_expr(&mod_name, &options, ignore_attr_ident);

let pre_main_attrs = pre_main_attrs();
let unsupported_error = unsupported_error(std_crate, attr_name);
let unsupported_error = unsupported_error(attr_name);

let generated_items = quote! {
#unsupported_error
Expand All @@ -561,7 +561,7 @@ pub fn bench_group(options: TokenStream, item: TokenStream) -> TokenStream {
#private_mod::EntryList::new({
static #static_ident: #private_mod::GroupEntry = #private_mod::GroupEntry {
meta: #meta,
generic_benches: #private_mod::None,
generic_benches: #option_none,
};

&#static_ident
Expand All @@ -581,7 +581,7 @@ fn entry_meta_expr(
options: &AttrOptions,
ignore_attr_ident: Option<&syn::Path>,
) -> proc_macro2::TokenStream {
let AttrOptions { private_mod, std_crate, .. } = &options;
let AttrOptions { private_mod, .. } = &options;

let raw_name_pretty = raw_name.strip_prefix("r#").unwrap_or(raw_name);

Expand All @@ -596,17 +596,17 @@ fn entry_meta_expr(
#private_mod::EntryMeta {
raw_name: #raw_name,
display_name: #display_name,
module_path: #std_crate::module_path!(),
module_path: ::std::module_path!(),

// `Span` location info is nightly-only, so use macros.
location: #private_mod::EntryLocation {
file: #std_crate::file!(),
line: #std_crate::line!(),
col: #std_crate::column!(),
file: ::std::file!(),
line: ::std::line!(),
col: ::std::column!(),
},

get_bench_options: #bench_options_fn,
cached_bench_options: #private_mod::OnceLock::new(),
cached_bench_options: ::std::sync::OnceLock::new(),
}
}
}
15 changes: 15 additions & 0 deletions macros/src/tokens.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//! Token generation utilities.
//!
//! These use items from the standard library as `::std`. This works unless
//! users do `extern crate x as std`, which is extremely unlikely.
use proc_macro2::TokenStream;
use quote::quote;

pub fn option_some() -> TokenStream {
quote!(::std::option::Option::Some)
}

pub fn option_none() -> TokenStream {
quote!(::std::option::Option::None)
}
6 changes: 3 additions & 3 deletions src/private.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub use std::{
self, any, borrow::Cow, default::Default, iter::FromIterator, option::Option::*, sync::OnceLock,
use std::{
borrow::{Borrow, Cow},
fmt::Debug,
};
use std::{borrow::Borrow, fmt::Debug};

pub use crate::{
bench::{BenchArgs, BenchOptions},
Expand Down

0 comments on commit 646419c

Please sign in to comment.