Make std usage optional for wgpu-core. (#7279)

* Make `wgpu_core::snatch::LockTrace` fully instead of partially conditional.

Now, when `cfg(not(debug_assertions))`, there is no `SNATCH_LOCK_TRACE`
thread local, and `LockTrace` has no data fields.

* Make `std` usage optional for `wgpu-core`.

Adds a `std` feature, enabled by default, to `wgpu-core`. When that
feature is disabled, the following functionality is not available:

* `Send + Sync` for resources.
* `trace` feature.
* `observe_locks` feature.
* Snatch lock recursive locking assertion.
This commit is contained in:
Kevin Reid 2025-03-06 11:07:38 -08:00 committed by GitHub
parent 1b09a44af5
commit 16d41cfaf3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 105 additions and 80 deletions

View File

@ -37,6 +37,11 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wgpu_validate_locks)'] }
[features]
#! See docuemntation for the `wgpu` crate for more in-depth information on these features.
# TODO(https://github.com/gfx-rs/wgpu/issues/6826): "std" is a default feature for
# compatibility with prior behavior only, and should be removed once we know how
# wgpu-cores dependents want to handle no_std.
default = ["std"]
#! ### Logging Configuration
# --------------------------------------------------------------------
@ -62,7 +67,7 @@ strict_asserts = ["wgpu-types/strict_asserts"]
# --------------------------------------------------------------------
## Enable lock order observation.
observe_locks = ["dep:ron", "serde/serde_derive"]
observe_locks = ["std", "dep:ron", "serde/serde_derive"]
#! ### Serialization
# --------------------------------------------------------------------
@ -71,7 +76,7 @@ observe_locks = ["dep:ron", "serde/serde_derive"]
serde = ["dep:serde", "wgpu-types/serde", "arrayvec/serde", "hashbrown/serde"]
## Enable API tracing.
trace = ["dep:ron", "serde", "naga/serialize"]
trace = ["serde", "std", "dep:ron", "naga/serialize"]
## Enable API replaying
replay = ["serde", "naga/deserialize"]
@ -107,6 +112,10 @@ fragile-send-sync-non-atomic-wasm = [
"wgpu-hal/fragile-send-sync-non-atomic-wasm",
]
## Enable certain items to be `Send` and `Sync` when they would not otherwise be.
## Also enables backtraces in some error cases when also under cfg(debug_assertions).
std = []
#! ### External libraries
# --------------------------------------------------------------------
#! The following features facilitate integration with third-party supporting libraries.

View File

@ -1,9 +1,12 @@
fn main() {
cfg_aliases::cfg_aliases! {
windows_linux_android: { any(windows, target_os = "linux", target_os = "android") },
send_sync: { any(
not(target_arch = "wasm32"),
all(feature = "fragile-send-sync-non-atomic-wasm", not(target_feature = "atomics"))
send_sync: { all(
feature = "std",
any(
not(target_arch = "wasm32"),
all(feature = "fragile-send-sync-non-atomic-wasm", not(target_feature = "atomics"))
)
) },
dx12: { all(target_os = "windows", feature = "dx12") },
webgl: { all(target_arch = "wasm32", not(target_os = "emscripten"), feature = "webgl") },

View File

@ -6,7 +6,6 @@ use alloc::{
vec::Vec,
};
use core::{fmt, mem::ManuallyDrop, ops::Range};
use std::sync::OnceLock;
use arrayvec::ArrayVec;
use thiserror::Error;
@ -602,7 +601,7 @@ pub struct BindGroupLayout {
/// We cannot unconditionally remove from the pool, as BGLs that don't come from the pool
/// (derived BGLs) must not be removed.
pub(crate) origin: bgl::Origin,
pub(crate) exclusive_pipeline: OnceLock<ExclusivePipeline>,
pub(crate) exclusive_pipeline: crate::OnceCellOrLock<ExclusivePipeline>,
#[allow(unused)]
pub(crate) binding_count_validator: BindingTypeMaxCountValidator,
/// The `label` from the descriptor used to create the resource.

View File

@ -11,7 +11,6 @@ use core::{
num::NonZeroU32,
sync::atomic::{AtomicBool, Ordering},
};
use std::sync::OnceLock;
use arrayvec::ArrayVec;
use bitflags::Flags;
@ -49,7 +48,7 @@ use crate::{
track::{BindGroupStates, DeviceTracker, TrackerIndexAllocators, UsageScope, UsageScopePool},
validation::{self, validate_color_attachment_bytes_per_sample},
weak_vec::WeakVec,
FastHashMap, LabelHelpers,
FastHashMap, LabelHelpers, OnceCellOrLock,
};
use super::{
@ -67,7 +66,7 @@ use portable_atomic::AtomicU64;
pub struct Device {
raw: Box<dyn hal::DynDevice>,
pub(crate) adapter: Arc<Adapter>,
pub(crate) queue: OnceLock<Weak<Queue>>,
pub(crate) queue: OnceCellOrLock<Weak<Queue>>,
pub(crate) zero_buffer: ManuallyDrop<Box<dyn hal::DynBuffer>>,
/// The `label` from the descriptor used to create the resource.
label: String,
@ -257,7 +256,7 @@ impl Device {
Ok(Self {
raw: raw_device,
adapter: adapter.clone(),
queue: OnceLock::new(),
queue: OnceCellOrLock::new(),
zero_buffer: ManuallyDrop::new(zero_buffer),
label: desc.label.to_string(),
command_allocator,
@ -1986,7 +1985,7 @@ impl Device {
device: self.clone(),
entries: entry_map,
origin,
exclusive_pipeline: OnceLock::new(),
exclusive_pipeline: OnceCellOrLock::new(),
binding_count_validator: count_validator,
label: label.to_string(),
};

View File

@ -63,7 +63,7 @@
#![cfg_attr(not(send_sync), allow(clippy::arc_with_non_send_sync))]
extern crate alloc;
// TODO(https://github.com/gfx-rs/wgpu/issues/6826): this should be optional
#[cfg(feature = "std")]
extern crate std;
extern crate wgpu_hal as hal;
extern crate wgpu_types as wgt;
@ -111,7 +111,6 @@ use alloc::{
borrow::{Cow, ToOwned as _},
string::String,
};
use std::os::raw::c_char;
pub(crate) use hash_utils::*;
@ -123,7 +122,7 @@ pub type SubmissionIndex = hal::FenceValue;
type Index = u32;
type Epoch = u32;
pub type RawString = *const c_char;
pub type RawString = *const core::ffi::c_char;
pub type Label<'a> = Option<Cow<'a, str>>;
trait LabelHelpers<'a> {
@ -227,17 +226,27 @@ pub(crate) fn get_greatest_common_divisor(mut a: u32, mut b: u32) -> u32 {
}
}
#[test]
fn test_lcd() {
assert_eq!(get_lowest_common_denom(2, 2), 2);
assert_eq!(get_lowest_common_denom(2, 3), 6);
assert_eq!(get_lowest_common_denom(6, 4), 12);
}
#[cfg(not(feature = "std"))]
use core::cell::OnceCell as OnceCellOrLock;
#[cfg(feature = "std")]
use std::sync::OnceLock as OnceCellOrLock;
#[test]
fn test_gcd() {
assert_eq!(get_greatest_common_divisor(5, 1), 1);
assert_eq!(get_greatest_common_divisor(4, 2), 2);
assert_eq!(get_greatest_common_divisor(6, 4), 2);
assert_eq!(get_greatest_common_divisor(7, 7), 7);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lcd() {
assert_eq!(get_lowest_common_denom(2, 2), 2);
assert_eq!(get_lowest_common_denom(2, 3), 6);
assert_eq!(get_lowest_common_denom(6, 4), 12);
}
#[test]
fn test_gcd() {
assert_eq!(get_greatest_common_divisor(5, 1), 1);
assert_eq!(get_greatest_common_divisor(4, 2), 2);
assert_eq!(get_greatest_common_divisor(6, 4), 2);
assert_eq!(get_greatest_common_divisor(7, 7), 7);
}
}

View File

@ -33,6 +33,7 @@
pub mod rank;
#[cfg(feature = "std")] // requires thread-locals to work
#[cfg_attr(not(wgpu_validate_locks), allow(dead_code))]
mod ranked;

View File

@ -1,18 +1,11 @@
#![allow(unused)]
use core::{
cell::{Cell, RefCell, UnsafeCell},
fmt,
panic::{self, Location},
};
use std::{backtrace::Backtrace, thread};
use core::{cell::UnsafeCell, fmt};
use crate::lock::{rank, RwLock, RwLockReadGuard, RwLockWriteGuard};
/// A guard that provides read access to snatchable data.
pub struct SnatchGuard<'a>(RwLockReadGuard<'a, ()>);
pub struct SnatchGuard<'a>(#[expect(dead_code)] RwLockReadGuard<'a, ()>);
/// A guard that allows snatching the snatchable data.
pub struct ExclusiveSnatchGuard<'a>(RwLockWriteGuard<'a, ()>);
pub struct ExclusiveSnatchGuard<'a>(#[expect(dead_code)] RwLockWriteGuard<'a, ()>);
/// A value that is mostly immutable but can be "snatched" if we need to destroy
/// it early.
@ -31,6 +24,7 @@ impl<T> Snatchable<T> {
}
}
#[allow(dead_code)]
pub fn empty() -> Self {
Snatchable {
value: UnsafeCell::new(None),
@ -66,58 +60,69 @@ impl<T> fmt::Debug for Snatchable<T> {
unsafe impl<T> Sync for Snatchable<T> {}
struct LockTrace {
purpose: &'static str,
caller: &'static Location<'static>,
backtrace: Backtrace,
}
use trace::LockTrace;
#[cfg(all(debug_assertions, feature = "std"))]
mod trace {
use core::{cell::Cell, fmt, panic::Location};
use std::{backtrace::Backtrace, thread};
impl fmt::Display for LockTrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"a {} lock at {}\n{}",
self.purpose, self.caller, self.backtrace
)
pub(super) struct LockTrace {
purpose: &'static str,
caller: &'static Location<'static>,
backtrace: Backtrace,
}
}
#[cfg(debug_assertions)]
impl LockTrace {
#[track_caller]
fn enter(purpose: &'static str) {
let new = LockTrace {
purpose,
caller: Location::caller(),
backtrace: Backtrace::capture(),
};
if let Some(prev) = SNATCH_LOCK_TRACE.take() {
let current = thread::current();
let name = current.name().unwrap_or("<unnamed>");
panic!(
"thread '{name}' attempted to acquire a snatch lock recursively.\n\
- Currently trying to acquire {new}\n\
- Previously acquired {prev}",
);
} else {
SNATCH_LOCK_TRACE.set(Some(new));
impl fmt::Display for LockTrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"a {} lock at {}\n{}",
self.purpose, self.caller, self.backtrace
)
}
}
fn exit() {
SNATCH_LOCK_TRACE.take();
impl LockTrace {
#[track_caller]
pub(super) fn enter(purpose: &'static str) {
let new = LockTrace {
purpose,
caller: Location::caller(),
backtrace: Backtrace::capture(),
};
if let Some(prev) = SNATCH_LOCK_TRACE.take() {
let current = thread::current();
let name = current.name().unwrap_or("<unnamed>");
panic!(
"thread '{name}' attempted to acquire a snatch lock recursively.\n\
- Currently trying to acquire {new}\n\
- Previously acquired {prev}",
);
} else {
SNATCH_LOCK_TRACE.set(Some(new));
}
}
pub(super) fn exit() {
SNATCH_LOCK_TRACE.take();
}
}
std::thread_local! {
static SNATCH_LOCK_TRACE: Cell<Option<LockTrace>> = const { Cell::new(None) };
}
}
#[cfg(not(all(debug_assertions, feature = "std")))]
mod trace {
pub(super) struct LockTrace {
_private: (),
}
#[cfg(not(debug_assertions))]
impl LockTrace {
fn enter(purpose: &'static str) {}
fn exit() {}
}
std::thread_local! {
static SNATCH_LOCK_TRACE: Cell<Option<LockTrace>> = const { Cell::new(None) };
impl LockTrace {
pub(super) fn enter(_purpose: &'static str) {}
pub(super) fn exit() {}
}
}
/// A Device-global lock for all snatchable data.