From 5dfc2c82854eede618f35c4511e941506f0e76d0 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 30 Sep 2019 16:02:01 -0400 Subject: [PATCH 1/6] Add support for declaring 'const fn' Add a new feature to enable this, since `const extern fn` support is unstable --- Cargo.toml | 1 + README.md | 3 ++ src/lib.rs | 1 + src/macros.rs | 98 ++++++++++++++++++++++++++++++++++---- src/unix/linux_like/mod.rs | 8 ++-- 5 files changed, 99 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e48a34cdfe9a2..a0658132492b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ std = [] align = [] rustc-dep-of-std = ['align', 'rustc-std-workspace-core'] extra_traits = [] +const-extern-fn = [] # use_std is deprecated, use `std` instead use_std = [ 'std' ] diff --git a/README.md b/README.md index 5371916acfec3..dc1d6d05de029 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,9 @@ libc = "0.2" * `extra_traits`: all `struct`s implemented in `libc` are `Copy` and `Clone`. This feature derives `Debug`, `Eq`, `Hash`, and `PartialEq`. +* `const-extern-fn`: Changes some `extern fn`s into `const extern fn`s. + This features requires a nightly rustc + * **deprecated**: `use_std` is deprecated, and is equivalent to `std`. ## Rust version support diff --git a/src/lib.rs b/src/lib.rs index 3255303e5a9b0..40625ac98dc69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ #![no_std] #![cfg_attr(feature = "rustc-dep-of-std", no_core)] #![cfg_attr(target_os = "redox", feature(static_nobundle))] +#![cfg_attr(feature = "const-extern-fn", feature(const_extern_fn))] #[macro_use] mod macros; diff --git a/src/macros.rs b/src/macros.rs index 14a28046640c6..da39270633274 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -121,16 +121,96 @@ macro_rules! s_no_extra_traits { ); } -#[allow(unused_macros)] -macro_rules! f { - ($(pub fn $i:ident($($arg:ident: $argty:ty),*) -> $ret:ty { - $($body:stmt);* - })*) => ($( - #[inline] - pub unsafe extern fn $i($($arg: $argty),*) -> $ret { - $($body);* +// This is a pretty horrible hack to allow us to conditionally mark +// some functions as 'const', without requiring users of this macro +// to care about the "const-extern-fn" feature. +// +// When 'const-extern-fn' is enabled, we emit the captured 'const' keyword +// in the expanded function. +// +// When 'const-extern-fn' is disabled, we always emit a plain 'pub unsafe extern fn'. +// Note that the expression matched by the macro is exactly the same - this allows +// users of this macro to work whether or not 'const-extern-fn' is enabled +// +// Unfortunately, we need to duplicate most of this macro between the 'cfg_if' blocks. +// This is because 'const unsafe extern fn' won't even parse on older compilers, +// so we need to avoid emitting it at all of 'const-extern-fn'. +// +// Specifically, moving the 'cfg_if' into the macro body will *not* work. +// Doing so would cause the '#[cfg(feature = "const-extern-fn")]' to be emiited +// into user code. The 'cfg' gate will not stop Rust from trying to parse the +// 'pub const unsafe extern fn', so users would get a compiler error even when +// the 'const-extern-fn' feature is disabled +// +// Note that users of this macro need to place 'const' in a weird position +// (after the closing ')' for the arguments, but before the return type). +// This was the only way I could satisfy the following two requirements: +// 1. Avoid ambuguity errors from 'macro_rules!' (which happen when writing '$foo:ident fn' +// 2. Allow users of this macro to mix 'pub fn foo' and 'pub const fn bar' within the same +// 'f!' block +cfg_if! { + if #[cfg(feature = "const-extern-fn")] { + #[allow(unused_macros)] + macro_rules! f { + ($(pub $({$constness:ident})* fn $i:ident( + $($arg:ident: $argty:ty),* + ) -> $ret:ty { + $($body:stmt);* + })*) => ($( + #[inline] + pub $($constness)* unsafe extern fn $i($($arg: $argty),* + ) -> $ret { + $($body);* + } + )*) } - )*) + + #[allow(unused_macros)] + macro_rules! const_fn { + ($($({$constness:ident})* fn $i:ident( + $($arg:ident: $argty:ty),* + ) -> $ret:ty { + $($body:stmt);* + })*) => ($( + #[inline] + $($constness)* fn $i($($arg: $argty),* + ) -> $ret { + $($body);* + } + )*) + } + + } else { + #[allow(unused_macros)] + macro_rules! f { + ($(pub $({$constness:ident})* fn $i:ident( + $($arg:ident: $argty:ty),* + ) -> $ret:ty { + $($body:stmt);* + })*) => ($( + #[inline] + pub unsafe extern fn $i($($arg: $argty),* + ) -> $ret { + $($body);* + } + )*) + } + + #[allow(unused_macros)] + macro_rules! const_fn { + ($($({$constness:ident})* fn $i:ident( + $($arg:ident: $argty:ty),* + ) -> $ret:ty { + $($body:stmt);* + })*) => ($( + #[inline] + fn $i($($arg: $argty),* + ) -> $ret { + $($body);* + } + )*) + } + } } #[allow(unused_macros)] diff --git a/src/unix/linux_like/mod.rs b/src/unix/linux_like/mod.rs index 1568e4f8369f1..a6ec553eeeb87 100644 --- a/src/unix/linux_like/mod.rs +++ b/src/unix/linux_like/mod.rs @@ -1165,8 +1165,10 @@ pub const ARPHRD_IEEE802154: u16 = 804; pub const ARPHRD_VOID: u16 = 0xFFFF; pub const ARPHRD_NONE: u16 = 0xFFFE; -fn CMSG_ALIGN(len: usize) -> usize { - len + ::mem::size_of::() - 1 & !(::mem::size_of::() - 1) +const_fn! { + {const} fn CMSG_ALIGN(len: usize) -> usize { + len + ::mem::size_of::() - 1 & !(::mem::size_of::() - 1) + } } f! { @@ -1182,7 +1184,7 @@ f! { cmsg.offset(1) as *mut ::c_uchar } - pub fn CMSG_SPACE(length: ::c_uint) -> ::c_uint { + pub {const} fn CMSG_SPACE(length: ::c_uint) -> ::c_uint { (CMSG_ALIGN(length as usize) + CMSG_ALIGN(::mem::size_of::())) as ::c_uint } From add284a294bacf80f6f39988aa9b3c13a4140908 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 28 Oct 2019 20:12:47 -0400 Subject: [PATCH 2/6] Test 'const-extern-fn' on nightly --- ci/build.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ci/build.sh b/ci/build.sh index a970b0e9c970a..cde46cb451b8d 100644 --- a/ci/build.sh +++ b/ci/build.sh @@ -67,6 +67,13 @@ test_target() { cargo "+${RUST}" "${BUILD_CMD}" -vv $opt --no-default-features --target "${TARGET}" \ --features extra_traits + # Test the 'const-extern-fn' feature on nightly + if [ "${RUST}" = "nightly" ]; then + cargo "+${RUST}" "${BUILD_CMD}" -vv $opt --no-default-features --target "${TARGET}" \ + --features const-extern-fn + fi + + # Also test that it builds with `extra_traits` and default features: if [ "$NO_STD" != "1" ]; then cargo "+${RUST}" "${BUILD_CMD}" -vv $opt --target "${TARGET}" \ From 457d65439610ae70b38e05ac83dde1419670727a Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 28 Oct 2019 20:14:12 -0400 Subject: [PATCH 3/6] Fix feature name Co-Authored-By: gnzlbg --- src/lib.rs | 2 +- src/macros.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 40625ac98dc69..0b1496af1d6c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ #![no_std] #![cfg_attr(feature = "rustc-dep-of-std", no_core)] #![cfg_attr(target_os = "redox", feature(static_nobundle))] -#![cfg_attr(feature = "const-extern-fn", feature(const_extern_fn))] +#![cfg_attr(libc_const_extern_fn, feature(const_extern_fn))] #[macro_use] mod macros; diff --git a/src/macros.rs b/src/macros.rs index da39270633274..f14bbf5522137 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -149,7 +149,7 @@ macro_rules! s_no_extra_traits { // 2. Allow users of this macro to mix 'pub fn foo' and 'pub const fn bar' within the same // 'f!' block cfg_if! { - if #[cfg(feature = "const-extern-fn")] { + if #[cfg(libc_const_extern_fn)] { #[allow(unused_macros)] macro_rules! f { ($(pub $({$constness:ident})* fn $i:ident( From b0ba2de7676af63ba4c13d7184b3f630735e7e7b Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 28 Oct 2019 21:09:30 -0400 Subject: [PATCH 4/6] Add Linux test --- build.rs | 5 +++++ tests/const_fn.rs | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 tests/const_fn.rs diff --git a/build.rs b/build.rs index d5bcb5253a4c8..fb1aae9be42e8 100644 --- a/build.rs +++ b/build.rs @@ -7,6 +7,7 @@ fn main() { rustc_minor_version().expect("Failed to get rustc version"); let rustc_dep_of_std = env::var("CARGO_FEATURE_RUSTC_DEP_OF_STD").is_ok(); let align_cargo_feature = env::var("CARGO_FEATURE_ALIGN").is_ok(); + let const_extern_fn_cargo_feature = env::var("CARGO_FEATURE_CONST_EXTERN_FN").is_ok(); let libc_ci = env::var("LIBC_CI").is_ok(); if env::var("CARGO_FEATURE_USE_STD").is_ok() { @@ -72,6 +73,10 @@ fn main() { if rustc_dep_of_std { println!("cargo:rustc-cfg=libc_thread_local"); } + + if const_extern_fn_cargo_feature { + println!("cargo:rustc-cfg=libc_const_extern_fn"); + } } fn rustc_minor_version() -> Option { diff --git a/tests/const_fn.rs b/tests/const_fn.rs new file mode 100644 index 0000000000000..4c413c90e8faa --- /dev/null +++ b/tests/const_fn.rs @@ -0,0 +1,5 @@ +#![cfg(libc_const_extern_fn)] // If this does not hold, the file is empty + +#[cfg(target_os = "linux")] +const _FOO: libc::c_uint = unsafe { libc::CMSG_SPACE(1) }; +//^ if CMSG_SPACE is not const, this will fail to compile From 476b6759762514c353b8f3d6375d86d747084340 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Tue, 29 Oct 2019 08:25:59 -0400 Subject: [PATCH 5/6] Panic if const-extern-fn is not used on nightly --- build.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/build.rs b/build.rs index fb1aae9be42e8..dddc029e221a0 100644 --- a/build.rs +++ b/build.rs @@ -3,8 +3,8 @@ use std::process::Command; use std::str; fn main() { - let rustc_minor_ver = - rustc_minor_version().expect("Failed to get rustc version"); + let (rustc_minor_ver, is_nightly) = + rustc_minor_nightly().expect("Failed to get rustc version"); let rustc_dep_of_std = env::var("CARGO_FEATURE_RUSTC_DEP_OF_STD").is_ok(); let align_cargo_feature = env::var("CARGO_FEATURE_ALIGN").is_ok(); let const_extern_fn_cargo_feature = env::var("CARGO_FEATURE_CONST_EXTERN_FN").is_ok(); @@ -75,11 +75,14 @@ fn main() { } if const_extern_fn_cargo_feature { + if !is_nightly || rustc_minor_ver < 40 { + panic!("const-extern-fn requires a nightly compiler >= 1.40") + } println!("cargo:rustc-cfg=libc_const_extern_fn"); } } -fn rustc_minor_version() -> Option { +fn rustc_minor_nightly() -> Option<(u32, bool)> { macro_rules! otry { ($e:expr) => { match $e { @@ -98,7 +101,12 @@ fn rustc_minor_version() -> Option { return None; } - otry!(pieces.next()).parse().ok() + let minor = pieces.next(); + let nightly_raw = otry!(otry!(pieces.next()).split('-').nth(1)); + let nightly = nightly_raw.starts_with("dev") || nightly_raw.starts_with("nightly"); + let minor = otry!(otry!(minor).parse().ok()); + + Some((minor, nightly)) } fn which_freebsd() -> Option { From ca2d53e281fa287dfa4f6bfe8768b20c9705c44f Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Tue, 29 Oct 2019 09:51:44 -0400 Subject: [PATCH 6/6] Run 'cargo fmt' --- build.rs | 6 ++++-- tests/const_fn.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build.rs b/build.rs index dddc029e221a0..420e15965121b 100644 --- a/build.rs +++ b/build.rs @@ -7,7 +7,8 @@ fn main() { rustc_minor_nightly().expect("Failed to get rustc version"); let rustc_dep_of_std = env::var("CARGO_FEATURE_RUSTC_DEP_OF_STD").is_ok(); let align_cargo_feature = env::var("CARGO_FEATURE_ALIGN").is_ok(); - let const_extern_fn_cargo_feature = env::var("CARGO_FEATURE_CONST_EXTERN_FN").is_ok(); + let const_extern_fn_cargo_feature = + env::var("CARGO_FEATURE_CONST_EXTERN_FN").is_ok(); let libc_ci = env::var("LIBC_CI").is_ok(); if env::var("CARGO_FEATURE_USE_STD").is_ok() { @@ -103,7 +104,8 @@ fn rustc_minor_nightly() -> Option<(u32, bool)> { let minor = pieces.next(); let nightly_raw = otry!(otry!(pieces.next()).split('-').nth(1)); - let nightly = nightly_raw.starts_with("dev") || nightly_raw.starts_with("nightly"); + let nightly = + nightly_raw.starts_with("dev") || nightly_raw.starts_with("nightly"); let minor = otry!(otry!(minor).parse().ok()); Some((minor, nightly)) diff --git a/tests/const_fn.rs b/tests/const_fn.rs index 4c413c90e8faa..0e7e1864b9f85 100644 --- a/tests/const_fn.rs +++ b/tests/const_fn.rs @@ -1,5 +1,5 @@ #![cfg(libc_const_extern_fn)] // If this does not hold, the file is empty #[cfg(target_os = "linux")] -const _FOO: libc::c_uint = unsafe { libc::CMSG_SPACE(1) }; +const _FOO: libc::c_uint = unsafe { libc::CMSG_SPACE(1) }; //^ if CMSG_SPACE is not const, this will fail to compile