Spatial Safety & Range Collisions (E1007, E1008)
The spatial borrow checker (SpatialBorrowChecker) validates that memory containers passed to an execution grid do not violate physical memory boundaries or overlap concurrently during execution.
1. Space Domain Bounds (error[E1008])
In the 1:1 SPMD model, each thread in Space is mapped directly to its corresponding scalar element in a GpuVec (thread i accesses element i).
If the global execution domain requires more threads than the container contains elements, trailing threads would calculate out-of-bounds Buffer Device Addresses, causing a GPU page fault or invalid memory read.
Dynamic Verification
Before submitting the dispatch, the runtime verifies:
container.len() >= space.size_x * space.size_y * space.size_z
If the capacity is insufficient, execution halts with diagnostic error[E1008]:
error[E1008]: GpuVec capacity is smaller than the requested space domain
--> src/main.rs:51:23
|
51 | scale_vectors.run(&Space::gpu_x(N), &mut vec);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ container contains fewer elements than required space cells
|
= note: each cell in `space` expects exclusive 1:1 access to its
corresponding element; extra threads would access out-of-bounds
memory.
= note: argument 1 (`GpuVec<f32>`) contains only 1000 elements
= note: space execution (dimensions: 10000x1x1) requires at least 10000
elements (deficit of 9000 elements)
= help: resize the `GpuVec` using `.resize(...)` to cover all space
cells, or adjust the space domain dimensions.
2. Spatial Slice Disjointness (error[E1007])
When multiple slices derived from the same physical root buffer are passed to a single nam dispatch, the runtime must verify that concurrent writes do not collide.
The Interval Intersection Algorithm
For any pair of slices A and B sharing the same underlying allocation root ID:
- If both are immutable (
&[T]), concurrent access is permitted. - If either slice is mutable (
&mut [T]), the engine computes their interval intersection:
overlap_start = max(A_start, B_start)
overlap_end = min(A_end, B_end)
If overlap_start < overlap_end, an overlapping memory range exists. The runtime halts execution with diagnostic error[E1007]:
error[E1007]: conflicting access to overlapping slices in nam dispatch
--> src/main.rs:20:24
|
20 | process_slices.run_unchecked(&Space::gpu_x(N), &mut slice_a, &slice_b);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable slice overlaps with an existing slice of the same GpuVec
|
= note: parallel threads executing in `space` cannot safely write to
memory that is concurrently being accessed by other threads.
= note: argument 1 covers elements [400..1000]
= note: argument 2 covers elements [0..500]
= note: spatial collision occurs on 100 overlapping elements [400..500]
of the parent GpuVec<f32>
= help: ensure sub-slices derived from the same parent GpuVec are
disjoint using `.split_at_mut()` or non-intersecting element
ranges.
The Idiomatic Solution: split_at_mut
To guarantee spatial disjointness without runtime interval checking, use standard Rust partitioning methods such as split_at_mut():
#![allow(unused)]
fn main() {
let mut vec = gpu_vec![2.0f32; 1000]; // parent vector
let mut output = gpu_vec![0.0f32; 1000]; // output vector
vec.copy_from_slice_at(500, &[10.0f32; 500]); // filling the parent vector with 10.0f32 after index 500.
let mut slice = vec.as_mut_slice(); // getting a mutable slice of the parent vector.
// `left` covers elements [0..500] and it holds the value of `2.0f32` and `right` covers elements [500..1000] and it holds the value of `10.0f32`
let (mut left, mut right) = slice.split_at_mut(500); // splitting the slice into two at index 500
process_slices.run_unchecked(&Space::gpu_x(N), &mut left, &mut right, &mut output);
}