diff --git a/CHANGELOG.md b/CHANGELOG.md index 96b17ed250..d71cac367f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,12 @@ the same every time it is rendered, we now warn if it is missing. - Expanded `StagingBelt` documentation by @kpreid in [#2905](https://github.com/gfx-rs/wgpu/pull/2905) +### Build Configuration + +- Add the `"strict_asserts"` feature, to enable additional internal + run-time validation in `wgpu-core`. By @jimblandy in + [#2872](https://github.com/gfx-rs/wgpu/pull/2872). + ## wgpu-0.13.2 (2022-07-13) ### Bug Fixes diff --git a/deno_webgpu/Cargo.toml b/deno_webgpu/Cargo.toml index ef7d25d254..2792c722db 100644 --- a/deno_webgpu/Cargo.toml +++ b/deno_webgpu/Cargo.toml @@ -14,5 +14,5 @@ description = "WebGPU implementation for Deno" deno_core = "0.144.0" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.19", features = ["full"] } -wgpu-core = { path = "../wgpu-core", features = ["trace", "replay", "serde"] } +wgpu-core = { path = "../wgpu-core", features = ["trace", "replay", "serde", "strict_asserts"] } wgpu-types = { path = "../wgpu-types", features = ["trace", "replay", "serde"] } diff --git a/player/Cargo.toml b/player/Cargo.toml index d5e712f4f7..6e751eca46 100644 --- a/player/Cargo.toml +++ b/player/Cargo.toml @@ -29,7 +29,7 @@ features = ["replay"] [dependencies.wgc] path = "../wgpu-core" package = "wgpu-core" -features = ["replay", "raw-window-handle"] +features = ["replay", "raw-window-handle", "strict_asserts"] [dev-dependencies] serde = "1" diff --git a/wgpu-core/Cargo.toml b/wgpu-core/Cargo.toml index 61bb72f6ca..73d81e64e7 100644 --- a/wgpu-core/Cargo.toml +++ b/wgpu-core/Cargo.toml @@ -13,6 +13,9 @@ license = "MIT OR Apache-2.0" [features] default = [] +# Apply run-time checks, even in release builds. These are in addition +# to the validation carried out at public APIs in all builds. +strict_asserts = [] angle = ["hal/gles"] # Enable API tracing trace = ["ron", "serde", "wgt/trace", "arrayvec/serde", "naga/serialize"] diff --git a/wgpu-core/src/assertions.rs b/wgpu-core/src/assertions.rs new file mode 100644 index 0000000000..98e8bd8797 --- /dev/null +++ b/wgpu-core/src/assertions.rs @@ -0,0 +1,49 @@ +//! Macros for validation internal to the resource tracker. +//! +//! This module defines assertion macros that respect `wgpu-core`'s +//! `"strict_asserts"` feature. +//! +//! Because `wgpu-core`'s public APIs validate their arguments in all +//! types of builds, for performance, the `track` module skips some of +//! Rust's usual run-time checks on its internal operations in release +//! builds. However, some `wgpu-core` applications have a strong +//! preference for robustness over performance. To accommodate them, +//! `wgpu-core`'s `"strict_asserts"` feature enables that validation +//! in both debug and release builds. + +#[cfg(feature = "strict_asserts")] +macro_rules! strict_assert { + ( $( $arg:tt )* ) => { + assert!( $( $arg )* ) + } +} + +#[cfg(feature = "strict_asserts")] +macro_rules! strict_assert_eq { + ( $( $arg:tt )* ) => { + assert_eq!( $( $arg )* ) + } +} + +#[cfg(feature = "strict_asserts")] +macro_rules! strict_assert_ne { + ( $( $arg:tt )* ) => { + assert_ne!( $( $arg )* ) + } +} + +#[cfg(not(feature = "strict_asserts"))] +#[macro_export] +macro_rules! strict_assert { + ( $( $arg:tt )* ) => {}; +} + +#[cfg(not(feature = "strict_asserts"))] +macro_rules! strict_assert_eq { + ( $( $arg:tt )* ) => {}; +} + +#[cfg(not(feature = "strict_asserts"))] +macro_rules! strict_assert_ne { + ( $( $arg:tt )* ) => {}; +} diff --git a/wgpu-core/src/lib.rs b/wgpu-core/src/lib.rs index be3c4c3368..245d16d18c 100644 --- a/wgpu-core/src/lib.rs +++ b/wgpu-core/src/lib.rs @@ -33,6 +33,9 @@ clippy::pattern_type_mismatch, )] +#[macro_use] +mod assertions; + pub mod binding_model; pub mod command; mod conv; diff --git a/wgpu-core/src/track/buffer.rs b/wgpu-core/src/track/buffer.rs index 2ece124a81..c67138eebe 100644 --- a/wgpu-core/src/track/buffer.rs +++ b/wgpu-core/src/track/buffer.rs @@ -101,9 +101,9 @@ impl BufferUsageScope { } } - fn debug_assert_in_bounds(&self, index: usize) { - debug_assert!(index < self.state.len()); - self.metadata.debug_assert_in_bounds(index); + fn tracker_assert_in_bounds(&self, index: usize) { + strict_assert!(index < self.state.len()); + self.metadata.tracker_assert_in_bounds(index); } /// Sets the size of all the vectors inside the tracker. @@ -179,8 +179,8 @@ impl BufferUsageScope { } for index in iterate_bitvec_indices(&scope.metadata.owned) { - self.debug_assert_in_bounds(index); - scope.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); + scope.tracker_assert_in_bounds(index); unsafe { insert_or_merge( @@ -225,7 +225,7 @@ impl BufferUsageScope { self.allow_index(index); - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); unsafe { insert_or_merge( @@ -265,10 +265,10 @@ impl BufferTracker { } } - fn debug_assert_in_bounds(&self, index: usize) { - debug_assert!(index < self.start.len()); - debug_assert!(index < self.end.len()); - self.metadata.debug_assert_in_bounds(index); + fn tracker_assert_in_bounds(&self, index: usize) { + strict_assert!(index < self.start.len()); + strict_assert!(index < self.end.len()); + self.metadata.tracker_assert_in_bounds(index); } /// Sets the size of all the vectors inside the tracker. @@ -311,7 +311,7 @@ impl BufferTracker { self.allow_index(index); - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); unsafe { let currently_owned = self.metadata.owned.get(index).unwrap_unchecked(); @@ -356,7 +356,7 @@ impl BufferTracker { self.allow_index(index); - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); unsafe { insert_or_barrier_update( @@ -373,7 +373,7 @@ impl BufferTracker { ) }; - debug_assert!(self.temp.len() <= 1); + strict_assert!(self.temp.len() <= 1); Some((value, self.temp.pop())) } @@ -393,8 +393,8 @@ impl BufferTracker { } for index in iterate_bitvec_indices(&tracker.metadata.owned) { - self.debug_assert_in_bounds(index); - tracker.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); + tracker.tracker_assert_in_bounds(index); unsafe { insert_or_barrier_update( None, @@ -433,8 +433,8 @@ impl BufferTracker { } for index in iterate_bitvec_indices(&scope.metadata.owned) { - self.debug_assert_in_bounds(index); - scope.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); + scope.tracker_assert_in_bounds(index); unsafe { insert_or_barrier_update( None, @@ -488,7 +488,7 @@ impl BufferTracker { let (index32, _, _) = id.0.unzip(); let index = index32 as usize; - scope.debug_assert_in_bounds(index); + scope.tracker_assert_in_bounds(index); if !scope.metadata.owned.get(index).unwrap_unchecked() { continue; @@ -529,7 +529,7 @@ impl BufferTracker { return false; } - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); unsafe { if self.metadata.owned.get(index).unwrap_unchecked() { @@ -569,7 +569,7 @@ impl BufferStateProvider<'_> { match *self { BufferStateProvider::Direct { state } => state, BufferStateProvider::Indirect { state } => { - debug_assert!(index < state.len()); + strict_assert!(index < state.len()); *state.get_unchecked(index) } } @@ -695,8 +695,8 @@ unsafe fn insert( // This should only ever happen with a wgpu bug, but let's just double // check that resource states don't have any conflicts. - debug_assert_eq!(invalid_resource_state(new_start_state), false); - debug_assert_eq!(invalid_resource_state(new_end_state), false); + strict_assert_eq!(invalid_resource_state(new_start_state), false); + strict_assert_eq!(invalid_resource_state(new_end_state), false); log::trace!("\tbuf {index}: insert {new_start_state:?}..{new_end_state:?}"); diff --git a/wgpu-core/src/track/mod.rs b/wgpu-core/src/track/mod.rs index f1a43b3297..c906ceb04d 100644 --- a/wgpu-core/src/track/mod.rs +++ b/wgpu-core/src/track/mod.rs @@ -143,13 +143,13 @@ impl PendingTransition { let texture = tex.inner.as_raw().expect("Texture is destroyed"); // These showing up in a barrier is always a bug - debug_assert_ne!(self.usage.start, hal::TextureUses::UNKNOWN); - debug_assert_ne!(self.usage.end, hal::TextureUses::UNKNOWN); + strict_assert_ne!(self.usage.start, hal::TextureUses::UNKNOWN); + strict_assert_ne!(self.usage.end, hal::TextureUses::UNKNOWN); let mip_count = self.selector.mips.end - self.selector.mips.start; - debug_assert_ne!(mip_count, 0); + strict_assert_ne!(mip_count, 0); let layer_count = self.selector.layers.end - self.selector.layers.start; - debug_assert_ne!(layer_count, 0); + strict_assert_ne!(layer_count, 0); hal::TextureBarrier { texture, @@ -351,12 +351,13 @@ impl ResourceMetadata { /// sanity checks of the presence of a refcount. /// /// In release mode this function is completely empty and is removed. - fn debug_assert_in_bounds(&self, index: usize) { - debug_assert!(index < self.owned.len()); - debug_assert!(index < self.ref_counts.len()); - debug_assert!(index < self.epochs.len()); + #[cfg_attr(not(feature = "strict_asserts"), allow(unused_variables))] + fn tracker_assert_in_bounds(&self, index: usize) { + strict_assert!(index < self.owned.len()); + strict_assert!(index < self.ref_counts.len()); + strict_assert!(index < self.epochs.len()); - debug_assert!(if self.owned.get(index).unwrap() { + strict_assert!(if self.owned.get(index).unwrap() { self.ref_counts[index].is_some() } else { true @@ -373,7 +374,7 @@ impl ResourceMetadata { /// Returns ids for all resources we own. fn used(&self) -> impl Iterator> + '_ { if !self.owned.is_empty() { - self.debug_assert_in_bounds(self.owned.len() - 1) + self.tracker_assert_in_bounds(self.owned.len() - 1) }; iterate_bitvec_indices(&self.owned).map(move |index| { let epoch = unsafe { *self.epochs.get_unchecked(index) }; @@ -418,7 +419,7 @@ impl ResourceMetadataProvider<'_, A> { (epoch, ref_count.into_owned()) } ResourceMetadataProvider::Indirect { metadata } => { - metadata.debug_assert_in_bounds(index); + metadata.tracker_assert_in_bounds(index); ( *metadata.epochs.get_unchecked(index), metadata @@ -429,7 +430,7 @@ impl ResourceMetadataProvider<'_, A> { ) } ResourceMetadataProvider::Resource { epoch } => { - debug_assert!(life_guard.is_some()); + strict_assert!(life_guard.is_some()); (epoch, life_guard.unwrap_unchecked().add_ref()) } } @@ -445,7 +446,7 @@ impl ResourceMetadataProvider<'_, A> { ResourceMetadataProvider::Direct { epoch, .. } | ResourceMetadataProvider::Resource { epoch, .. } => epoch, ResourceMetadataProvider::Indirect { metadata } => { - metadata.debug_assert_in_bounds(index); + metadata.tracker_assert_in_bounds(index); *metadata.epochs.get_unchecked(index) } } diff --git a/wgpu-core/src/track/stateless.rs b/wgpu-core/src/track/stateless.rs index a8051bb3ce..4267e829db 100644 --- a/wgpu-core/src/track/stateless.rs +++ b/wgpu-core/src/track/stateless.rs @@ -70,8 +70,8 @@ impl StatelessTracker { } } - fn debug_assert_in_bounds(&self, index: usize) { - self.metadata.debug_assert_in_bounds(index); + fn tracker_assert_in_bounds(&self, index: usize) { + self.metadata.tracker_assert_in_bounds(index); } /// Sets the size of all the vectors inside the tracker. @@ -106,7 +106,7 @@ impl StatelessTracker { self.allow_index(index); - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); unsafe { *self.metadata.epochs.get_unchecked_mut(index) = epoch; @@ -127,7 +127,7 @@ impl StatelessTracker { self.allow_index(index); - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); unsafe { *self.metadata.epochs.get_unchecked_mut(index) = epoch; @@ -149,8 +149,8 @@ impl StatelessTracker { } for index in iterate_bitvec_indices(&other.metadata.owned) { - self.debug_assert_in_bounds(index); - other.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); + other.tracker_assert_in_bounds(index); unsafe { let previously_owned = self.metadata.owned.get(index).unwrap_unchecked(); @@ -187,7 +187,7 @@ impl StatelessTracker { return false; } - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); unsafe { if self.metadata.owned.get(index).unwrap_unchecked() { diff --git a/wgpu-core/src/track/texture.rs b/wgpu-core/src/track/texture.rs index 0f157c8f15..4ad833dc05 100644 --- a/wgpu-core/src/track/texture.rs +++ b/wgpu-core/src/track/texture.rs @@ -100,18 +100,18 @@ impl ComplexTextureState { full_range: TextureSelector, state_iter: impl Iterator, ) -> Self { - debug_assert_eq!(full_range.layers.start, 0); - debug_assert_eq!(full_range.mips.start, 0); + strict_assert_eq!(full_range.layers.start, 0); + strict_assert_eq!(full_range.mips.start, 0); let mut complex = ComplexTextureState::new(full_range.mips.len() as u32, full_range.layers.len() as u32); for (selector, desired_state) in state_iter { - debug_assert!(selector.layers.end <= full_range.layers.end); - debug_assert!(selector.mips.end <= full_range.mips.end); + strict_assert!(selector.layers.end <= full_range.layers.end); + strict_assert!(selector.mips.end <= full_range.mips.end); // This should only ever happen with a wgpu bug, but let's just double // check that resource states don't have any conflicts. - debug_assert_eq!(invalid_resource_state(desired_state), false); + strict_assert_eq!(invalid_resource_state(desired_state), false); let mips = selector.mips.start as usize..selector.mips.end as usize; for mip in complex.mips.get_unchecked_mut(mips) { @@ -233,12 +233,12 @@ impl TextureUsageScope { } } - fn debug_assert_in_bounds(&self, index: usize) { - self.metadata.debug_assert_in_bounds(index); + fn tracker_assert_in_bounds(&self, index: usize) { + self.metadata.tracker_assert_in_bounds(index); - debug_assert!(index < self.set.simple.len()); + strict_assert!(index < self.set.simple.len()); - debug_assert!(if self.metadata.owned.get(index).unwrap() + strict_assert!(if self.metadata.owned.get(index).unwrap() && self.set.simple[index] == TextureUses::COMPLEX { self.set.complex.contains_key(&(index as u32)) @@ -288,8 +288,8 @@ impl TextureUsageScope { for index in iterate_bitvec_indices(&scope.metadata.owned) { let index32 = index as u32; - self.debug_assert_in_bounds(index); - scope.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); + scope.tracker_assert_in_bounds(index); unsafe { insert_or_merge( @@ -357,7 +357,7 @@ impl TextureUsageScope { let (index32, epoch, _) = id.0.unzip(); let index = index32 as usize; - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); insert_or_merge( texture_data_from_texture(storage, index32), @@ -401,20 +401,20 @@ impl TextureTracker { } } - fn debug_assert_in_bounds(&self, index: usize) { - self.metadata.debug_assert_in_bounds(index); + fn tracker_assert_in_bounds(&self, index: usize) { + self.metadata.tracker_assert_in_bounds(index); - debug_assert!(index < self.start_set.simple.len()); - debug_assert!(index < self.end_set.simple.len()); + strict_assert!(index < self.start_set.simple.len()); + strict_assert!(index < self.end_set.simple.len()); - debug_assert!(if self.metadata.owned.get(index).unwrap() + strict_assert!(if self.metadata.owned.get(index).unwrap() && self.start_set.simple[index] == TextureUses::COMPLEX { self.start_set.complex.contains_key(&(index as u32)) } else { true }); - debug_assert!(if self.metadata.owned.get(index).unwrap() + strict_assert!(if self.metadata.owned.get(index).unwrap() && self.end_set.simple[index] == TextureUses::COMPLEX { self.end_set.complex.contains_key(&(index as u32)) @@ -463,7 +463,7 @@ impl TextureTracker { let (index32, _, _) = id.0.unzip(); let index = index32 as usize; - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); self.metadata .ref_counts @@ -484,7 +484,7 @@ impl TextureTracker { self.allow_index(index); - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); unsafe { let currently_owned = self.metadata.owned.get(index).unwrap_unchecked(); @@ -531,7 +531,7 @@ impl TextureTracker { self.allow_index(index); - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); unsafe { insert_or_barrier_update( @@ -575,8 +575,8 @@ impl TextureTracker { for index in iterate_bitvec_indices(&tracker.metadata.owned) { let index32 = index as u32; - self.debug_assert_in_bounds(index); - tracker.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); + tracker.tracker_assert_in_bounds(index); unsafe { insert_or_barrier_update( texture_data_from_texture(storage, index32), @@ -621,8 +621,8 @@ impl TextureTracker { for index in iterate_bitvec_indices(&scope.metadata.owned) { let index32 = index as u32; - self.debug_assert_in_bounds(index); - scope.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); + scope.tracker_assert_in_bounds(index); unsafe { insert_or_barrier_update( texture_data_from_texture(storage, index32), @@ -674,7 +674,7 @@ impl TextureTracker { for &(id, _, _, _) in bind_group_state.textures.iter() { let (index32, _, _) = id.0.unzip(); let index = index32 as usize; - scope.debug_assert_in_bounds(index); + scope.tracker_assert_in_bounds(index); if !scope.metadata.owned.get(index).unwrap_unchecked() { continue; @@ -712,7 +712,7 @@ impl TextureTracker { return false; } - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); unsafe { if self.metadata.owned.get(index).unwrap_unchecked() { @@ -746,7 +746,7 @@ impl TextureTracker { return false; } - self.debug_assert_in_bounds(index); + self.tracker_assert_in_bounds(index); unsafe { if self.metadata.owned.get(index).unwrap_unchecked() { @@ -1014,7 +1014,7 @@ unsafe fn insert( SingleOrManyStates::Single(state) => { // This should only ever happen with a wgpu bug, but let's just double // check that resource states don't have any conflicts. - debug_assert_eq!(invalid_resource_state(state), false); + strict_assert_eq!(invalid_resource_state(state), false); log::trace!("\ttex {index32}: insert start {state:?}"); @@ -1052,7 +1052,7 @@ unsafe fn insert( SingleOrManyStates::Single(state) => { // This should only ever happen with a wgpu bug, but let's just double // check that resource states don't have any conflicts. - debug_assert_eq!(invalid_resource_state(state), false); + strict_assert_eq!(invalid_resource_state(state), false); log::trace!("\ttex {index32}: insert end {state:?}"); @@ -1199,7 +1199,7 @@ unsafe fn merge( (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Many(new_many)) => { for (selector, new_state) in new_many { for mip_id in selector.mips { - debug_assert!((mip_id as usize) < current_complex.mips.len()); + strict_assert!((mip_id as usize) < current_complex.mips.len()); let mip = current_complex.mips.get_unchecked_mut(mip_id as usize); @@ -1330,7 +1330,7 @@ unsafe fn barrier( (SingleOrManyStates::Many(current_complex), SingleOrManyStates::Many(new_many)) => { for (selector, new_state) in new_many { for mip_id in selector.mips { - debug_assert!((mip_id as usize) < current_complex.mips.len()); + strict_assert!((mip_id as usize) < current_complex.mips.len()); let mip = current_complex.mips.get_unchecked(mip_id as usize); @@ -1437,14 +1437,14 @@ unsafe fn update( // If this state is unknown, that means that the start is _also_ unknown. if current_layer_state == TextureUses::UNKNOWN { if let Some(&mut ref mut start_complex) = start_complex { - debug_assert!(mip_id < start_complex.mips.len()); + strict_assert!(mip_id < start_complex.mips.len()); let start_mip = start_complex.mips.get_unchecked_mut(mip_id); for &mut (_, ref mut current_start_state) in start_mip.isolate(layers, TextureUses::UNKNOWN) { - debug_assert_eq!(*current_start_state, TextureUses::UNKNOWN); + strict_assert_eq!(*current_start_state, TextureUses::UNKNOWN); *current_start_state = new_single; } @@ -1469,7 +1469,7 @@ unsafe fn update( for mip_id in selector.mips { let mip_id = mip_id as usize; - debug_assert!(mip_id < current_complex.mips.len()); + strict_assert!(mip_id < current_complex.mips.len()); let mip = current_complex.mips.get_unchecked_mut(mip_id); @@ -1484,18 +1484,18 @@ unsafe fn update( // We know we must have starter state be complex, otherwise we would know // about this state. - debug_assert!(start_complex.is_some()); + strict_assert!(start_complex.is_some()); let start_complex = start_complex.as_deref_mut().unwrap_unchecked(); - debug_assert!(mip_id < start_complex.mips.len()); + strict_assert!(mip_id < start_complex.mips.len()); let start_mip = start_complex.mips.get_unchecked_mut(mip_id); for &mut (_, ref mut current_start_state) in start_mip.isolate(layers, TextureUses::UNKNOWN) { - debug_assert_eq!(*current_start_state, TextureUses::UNKNOWN); + strict_assert_eq!(*current_start_state, TextureUses::UNKNOWN); *current_start_state = new_state; }