Skip to content

Commit bb9935c

Browse files
WIP: refactor(core)!: factor out new buffer_region_overrun module
1 parent 1fe9233 commit bb9935c

5 files changed

Lines changed: 131 additions & 148 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use thiserror::Error;
2+
use wgt::BufferAddress;
3+
4+
use crate::{
5+
command::{CopySide, TransferError},
6+
resource::BufferAccessError,
7+
};
8+
9+
/// Error encountered while checking offsets against a buffer.
10+
#[derive(Clone, Debug, Error)]
11+
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12+
#[non_exhaustive]
13+
pub enum BufferRegionOverrunError {
14+
#[error("start offset ({offset}) is out-of-bounds for buffer of size {buffer_size}")]
15+
StartOffset {
16+
offset: BufferAddress,
17+
buffer_size: BufferAddress,
18+
},
19+
#[error(
20+
"end offset (start at {} + size of {}) is out-of-bounds for buffer of size {}",
21+
offset,
22+
size,
23+
buffer_size
24+
)]
25+
EndOffset {
26+
offset: BufferAddress,
27+
size: BufferAddress,
28+
buffer_size: BufferAddress,
29+
},
30+
}
31+
32+
impl BufferRegionOverrunError {
33+
/// Returns the end offset if everything is validated.
34+
///
35+
/// TODO: more
36+
pub(crate) fn check(
37+
start_offset: BufferAddress,
38+
size: BufferAddress,
39+
buffer_size: BufferAddress,
40+
) -> Result<BufferAddress, Self> {
41+
Self::check_start(start_offset, buffer_size)?.check_end(size)
42+
}
43+
44+
pub(crate) fn check_start(
45+
start_offset: BufferAddress,
46+
buffer_size: BufferAddress,
47+
) -> Result<BufferOverrunEndOffsetChecker, Self> {
48+
if start_offset >= buffer_size {
49+
return Err(Self::StartOffset {
50+
offset: start_offset,
51+
buffer_size,
52+
});
53+
}
54+
Ok(BufferOverrunEndOffsetChecker {
55+
start_offset,
56+
buffer_size,
57+
})
58+
}
59+
}
60+
61+
/// A
62+
#[derive(Clone, Copy, Debug)]
63+
pub(crate) struct BufferOverrunEndOffsetChecker {
64+
start_offset: BufferAddress,
65+
buffer_size: BufferAddress,
66+
}
67+
68+
impl BufferOverrunEndOffsetChecker {
69+
pub(crate) fn check_end(
70+
self,
71+
size: BufferAddress,
72+
) -> Result<BufferAddress, BufferRegionOverrunError> {
73+
let Self {
74+
start_offset,
75+
buffer_size,
76+
} = self;
77+
78+
// NOTE: Should never underflow because of our earlier check.
79+
if size > buffer_size - start_offset {
80+
return Err(BufferRegionOverrunError::EndOffset {
81+
offset: start_offset,
82+
size,
83+
buffer_size,
84+
});
85+
}
86+
// NOTE: Should never overflow because of our earlier check.
87+
Ok(start_offset + size)
88+
}
89+
}
90+
91+
impl BufferRegionOverrunError {
92+
pub(crate) fn to_transfer_error(&self, side: CopySide) -> TransferError {
93+
todo!()
94+
}
95+
}

wgpu-core/src/command/transfer.rs

Lines changed: 18 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use wgt::{
1010

1111
use crate::{
1212
api_log,
13+
buffer_region_overrun::BufferRegionOverrunError,
1314
command::{
1415
clear_texture, encoder::EncodingState, ArcCommand, CommandEncoderError, EncoderStateError,
1516
},
@@ -47,24 +48,8 @@ pub enum TransferError {
4748
MissingBufferUsage(#[from] MissingBufferUsageError),
4849
#[error(transparent)]
4950
MissingTextureUsage(#[from] MissingTextureUsageError),
50-
#[error("Start offset ({offset}) is out-of-bounds for buffer of size {buffer_size}")]
51-
BufferStartOffsetOverrun {
52-
offset: BufferAddress,
53-
buffer_size: BufferAddress,
54-
side: CopySide,
55-
},
56-
#[error(
57-
"End offset (start at {} + size of {}) is out-of-bounds for buffer of size {}",
58-
offset,
59-
size,
60-
buffer_size
61-
)]
62-
BufferEndOffsetOverrun {
63-
offset: BufferAddress,
64-
size: BufferAddress,
65-
buffer_size: BufferAddress,
66-
side: CopySide,
67-
},
51+
#[error("Transfer {_0}")]
52+
BufferOverrun(BufferRegionOverrunError),
6853
#[error("Copy of {dimension:?} {start_offset}..{end_offset} would end up overrunning the bounds of the {side:?} texture of {dimension:?} size {texture_size}")]
6954
TextureOverrun {
7055
start_offset: u32,
@@ -192,9 +177,8 @@ impl WebGpuError for TransferError {
192177
Self::MissingTextureUsage(e) => e,
193178
Self::MemoryInitFailure(e) => e,
194179

195-
Self::BufferEndOffsetOverrun { .. }
180+
Self::BufferOverrun { .. }
196181
| Self::TextureOverrun { .. }
197-
| Self::BufferStartOffsetOverrun { .. }
198182
| Self::UnsupportedPartialTransfer { .. }
199183
| Self::InvalidCopyWithinSameTexture { .. }
200184
| Self::InvalidTextureAspect { .. }
@@ -352,22 +336,9 @@ pub(crate) fn validate_linear_texture_data(
352336
return Err(TransferError::UnspecifiedRowsPerImage);
353337
};
354338

355-
if offset > buffer_size {
356-
return Err(TransferError::BufferStartOffsetOverrun {
357-
start_offset: offset,
358-
buffer_size: bytes_in_copy,
359-
side: buffer_side,
360-
});
361-
}
362-
// NOTE: Should never underflow because of our earlier check.
363-
if bytes_in_copy > buffer_size - offset {
364-
return Err(TransferError::BufferEndOffsetOverrun {
365-
offset,
366-
size: bytes_in_copy,
367-
buffer_size,
368-
side: buffer_side,
369-
});
370-
}
339+
// Avoid underflow in the subtraction by checking bytes_in_copy against buffer_size first.
340+
let _ = BufferRegionOverrunError::check(offset, bytes_in_copy, buffer_size)
341+
.map_err(|e| e.to_transfer_error(buffer_side))?;
371342

372343
let is_contiguous = (row_stride_bytes == row_bytes_dense || !requires_multiple_rows)
373344
&& (image_stride_bytes == image_bytes_dense || !requires_multiple_images);
@@ -1000,15 +971,10 @@ pub(super) fn copy_buffer_to_buffer(
1000971
.map_err(TransferError::MissingBufferUsage)?;
1001972
let dst_barrier = dst_pending.map(|pending| pending.into_hal(dst_buffer, state.snatch_guard));
1002973

1003-
// TODO: This check isn't part of the spec., but it looks like it should be.
1004-
if source_offset > src_buffer.size {
1005-
return Err(TransferError::BufferStartOffsetOverrun {
1006-
offset: destination_offset,
1007-
buffer_size: dst_buffer.size,
1008-
side: CopySide::Destination,
1009-
}
1010-
.into());
1011-
}
974+
let src_end_offset_checker =
975+
BufferRegionOverrunError::check_start(source_offset, src_buffer.size)
976+
.map_err(|e| e.to_transfer_error(CopySide::Source))?;
977+
1012978
let size = size.unwrap_or_else(|| {
1013979
// NOTE: Should never underflow because of our earlier check.
1014980
src_buffer.size - source_offset
@@ -1045,40 +1011,13 @@ pub(super) fn copy_buffer_to_buffer(
10451011
}
10461012
}
10471013

1048-
if size > src_buffer.size - source_offset {
1049-
return Err(TransferError::BufferEndOffsetOverrun {
1050-
offset: source_offset,
1051-
size,
1052-
buffer_size: src_buffer.size,
1053-
side: CopySide::Source,
1054-
}
1055-
.into());
1056-
}
1057-
// NOTE: Should never overflow because of our earlier check.
1058-
let source_end_offset = source_offset + size;
1059-
1060-
// TODO: Checks for source buffer overrun don't quite match spec., but it seems important to do
1061-
// things in this order.
1062-
if destination_offset > dst_buffer.size {
1063-
return Err(TransferError::BufferStartOffsetOverrun {
1064-
offset: destination_offset,
1065-
buffer_size: dst_buffer.size,
1066-
side: CopySide::Destination,
1067-
}
1068-
.into());
1069-
}
1070-
// NOTE: Should never underflow because of our earlier check.
1071-
if size > dst_buffer.size - destination_offset {
1072-
return Err(TransferError::BufferEndOffsetOverrun {
1073-
offset: destination_offset,
1074-
size,
1075-
buffer_size: dst_buffer.size,
1076-
side: CopySide::Destination,
1077-
}
1078-
.into());
1079-
}
1080-
// NOTE: Should never overflow because of our earlier check.
1081-
let destination_end_offset = destination_offset + size;
1014+
let source_end_offset = src_end_offset_checker
1015+
.check_end(size)
1016+
.map_err(|e| e.to_transfer_error(CopySide::Source))?;
1017+
1018+
let destination_end_offset =
1019+
BufferRegionOverrunError::check(destination_offset, size, dst_buffer.size)
1020+
.map_err(|e| e.to_transfer_error(CopySide::Destination))?;
10821021

10831022
// This must happen after parameter validation (so that errors are reported
10841023
// as required by the spec), but before any side effects.

wgpu-core/src/device/queue.rs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use super::{life::LifetimeTracker, Device};
1818
use crate::device::trace::{Action, IntoTrace};
1919
use crate::{
2020
api_log,
21+
buffer_region_overrun::BufferRegionOverrunError,
2122
command::{
2223
extract_texture_selector, validate_linear_texture_data, validate_texture_buffer_copy,
2324
validate_texture_copy_dst_format, validate_texture_copy_range, ClearError,
@@ -661,21 +662,8 @@ impl Queue {
661662
return Err(TransferError::UnalignedBufferOffset(buffer_offset));
662663
}
663664

664-
if buffer_offset > buffer.size {
665-
return Err(TransferError::BufferStartOffsetOverrun {
666-
offset: buffer_offset,
667-
buffer_size: buffer_size.get(),
668-
side: CopySide::Destination,
669-
});
670-
}
671-
if buffer_size.get() > buffer.size - buffer_offset {
672-
return Err(TransferError::BufferEndOffsetOverrun {
673-
offset: buffer_offset,
674-
size: buffer_size.get(),
675-
buffer_size: buffer.size,
676-
side: CopySide::Destination,
677-
});
678-
}
665+
let _ = BufferRegionOverrunError::check(buffer_offset, buffer_size.get(), buffer.size)
666+
.map_err(|e| e.to_transfer_error(CopySide::Destination))?;
679667

680668
Ok(())
681669
}

wgpu-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ extern crate wgpu_types as wgt;
6767

6868
mod as_hal;
6969
pub mod binding_model;
70+
pub(crate) mod buffer_region_overrun;
7071
pub mod command;
7172
mod conv;
7273
pub mod device;

wgpu-core/src/resource.rs

Lines changed: 14 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use wgt::{
1818
use crate::device::trace;
1919
use crate::{
2020
binding_model::{BindGroup, BindingError},
21+
buffer_region_overrun::BufferRegionOverrunError,
2122
device::{
2223
queue, resource::DeferredDestroy, BufferMapPendingClosure, Device, DeviceError,
2324
DeviceMismatch, HostMap, MissingDownlevelFlags, MissingFeatures,
@@ -304,22 +305,8 @@ pub enum BufferAccessError {
304305
MapAborted,
305306
#[error(transparent)]
306307
InvalidResource(#[from] InvalidResourceError),
307-
#[error("Map start offset ({offset}) is out-of-bounds for buffer of size {buffer_size}")]
308-
MapStartOffsetOverrun {
309-
offset: wgt::BufferAddress,
310-
buffer_size: wgt::BufferAddress,
311-
},
312-
#[error(
313-
"Map end offset (start at {} + size of {}) is out-of-bounds for buffer of size {}",
314-
offset,
315-
size,
316-
buffer_size
317-
)]
318-
MapEndOffsetOverrun {
319-
offset: wgt::BufferAddress,
320-
size: wgt::BufferAddress,
321-
buffer_size: wgt::BufferAddress,
322-
},
308+
#[error("Map {_0}")]
309+
MapOverrun(BufferRegionOverrunError),
323310
}
324311

325312
impl WebGpuError for BufferAccessError {
@@ -341,8 +328,7 @@ impl WebGpuError for BufferAccessError {
341328
| Self::OutOfBoundsStartOffsetOverrun { .. }
342329
| Self::OutOfBoundsEndOffsetOverrun { .. }
343330
| Self::MapAborted
344-
| Self::MapStartOffsetOverrun { .. }
345-
| Self::MapEndOffsetOverrun { .. } => return ErrorType::Validation,
331+
| Self::MapOverrun { .. } => return ErrorType::Validation,
346332
};
347333
e.webgpu_error_type()
348334
}
@@ -613,26 +599,12 @@ impl Buffer {
613599
return Err((op, BufferAccessError::UnalignedRangeSize { range_size }));
614600
}
615601

616-
if offset > self.size {
617-
return Err((
618-
op,
619-
BufferAccessError::MapStartOffsetOverrun {
620-
offset,
621-
buffer_size: self.size,
622-
},
623-
));
624-
}
625-
// NOTE: Should never underflow because of our earlier check.
626-
if range_size > self.size - offset {
627-
return Err((
628-
op,
629-
BufferAccessError::MapEndOffsetOverrun {
630-
offset,
631-
size: range_size,
632-
buffer_size: self.size,
633-
},
634-
));
635-
}
602+
let end_offset = match BufferRegionOverrunError::check(offset, range_size, self.size)
603+
.map_err(BufferAccessError::MapOverrun)
604+
{
605+
Ok(ok) => ok,
606+
Err(e) => return Err((op, e)),
607+
};
636608

637609
if !offset.is_multiple_of(wgt::MAP_ALIGNMENT)
638610
|| !end_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT)
@@ -671,8 +643,8 @@ impl Buffer {
671643
return Err((op, BufferAccessError::MapAlreadyPending));
672644
}
673645
BufferMapState::Idle => BufferMapState::Waiting(BufferPendingMapping {
674-
// NOTE: Should never overflow because of our earlier check.
675-
range: offset..end_offset,
646+
// NOTE: Should never overflow because of our earlier check.
647+
range: offset..end_offset,
676648
op,
677649
_parent_buffer: self.clone(),
678650
}),
@@ -726,20 +698,8 @@ impl Buffer {
726698
let map_state = &*self.map_state.lock();
727699
match *map_state {
728700
BufferMapState::Init { ref staging_buffer } => {
729-
if offset > self.size {
730-
return Err(BufferAccessError::MapStartOffsetOverrun {
731-
offset,
732-
buffer_size: self.size,
733-
});
734-
}
735-
// NOTE: Should never underflow because of our earlier check.
736-
if range_size > self.size - offset {
737-
return Err(BufferAccessError::MapEndOffsetOverrun {
738-
offset,
739-
size: range_size,
740-
buffer_size: self.size,
741-
});
742-
}
701+
let _ = BufferRegionOverrunError::check(offset, range_size, self.size)
702+
.map_err(BufferAccessError::MapOverrun)?;
743703
let ptr = unsafe { staging_buffer.ptr() };
744704
let ptr = unsafe { NonNull::new_unchecked(ptr.as_ptr().offset(offset as isize)) };
745705
Ok((ptr, range_size))

0 commit comments

Comments
 (0)