Skip to main content

script_webgpu/
datablock.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::ffi::c_void;
6use std::ops::Range;
7use std::sync::Arc;
8
9use js::context::JSContext;
10use js::rooted;
11use js::rust::wrappers2::{DetachArrayBuffer, NewExternalArrayBuffer};
12use js::typedarray::HeapArrayBuffer;
13use jstraceable_derive::JSTraceable;
14use malloc_size_of_derive::MallocSizeOf;
15use script_bindings::trace::RootedTraceableBox;
16
17#[derive(JSTraceable, MallocSizeOf)]
18#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
19pub(crate) struct DataBlock {
20    #[conditional_malloc_size_of]
21    data: Arc<Box<[u8]>>,
22    /// Data views (mutable subslices of data)
23    data_views: Vec<DataView>,
24}
25
26/// Returns true if two non-inclusive ranges overlap
27// https://stackoverflow.com/questions/3269434/whats-the-most-efficient-way-to-test-if-two-ranges-overlap
28fn range_overlap<T: std::cmp::PartialOrd>(range1: &Range<T>, range2: &Range<T>) -> bool {
29    range1.start < range2.end && range2.start < range1.end
30}
31
32impl DataBlock {
33    pub(crate) fn new_zeroed(size: usize) -> Self {
34        let data = vec![0; size];
35        Self {
36            data: Arc::new(data.into_boxed_slice()),
37            data_views: Vec::new(),
38        }
39    }
40
41    /// Panics if there is any active view or src data is not same length
42    pub(crate) fn load(&mut self, src: &[u8]) {
43        // `Arc::get_mut` ensures there are no views
44        Arc::get_mut(&mut self.data).unwrap().clone_from_slice(src)
45    }
46
47    /// Panics if there is any active view
48    pub(crate) fn data(&mut self) -> &mut [u8] {
49        // `Arc::get_mut` ensures there are no views
50        Arc::get_mut(&mut self.data).unwrap()
51    }
52
53    #[cfg_attr(
54        crown,
55        expect(
56            crown::unrooted_must_root,
57            reason = "Underlying content is rooted when GC can happen"
58        )
59    )]
60    pub(crate) fn clear_views(&mut self, cx: &mut JSContext) {
61        // we need to pop one by one so we can root one by one for detach
62        while let Some(DataView { buffer, .. }) = self.data_views.pop() {
63            rooted!(&in(cx) let b = unsafe { buffer.underlying_object().get() });
64            assert!(unsafe { DetachArrayBuffer(cx, b.handle()) })
65        }
66    }
67
68    /// Returns error if requested range is already mapped
69    pub(crate) fn view(
70        &mut self,
71        cx: &mut JSContext,
72        range: Range<usize>,
73    ) -> Result<&DataView, ()> {
74        if self
75            .data_views
76            .iter()
77            .any(|view| range_overlap(&view.range, &range))
78        {
79            return Err(());
80        }
81        let range_len = range
82            .end
83            .checked_sub(range.start)
84            .expect("range end must be >= range start");
85        assert!(range.end <= self.data.len());
86
87        /// `freeFunc()` must be threadsafe, should be safely callable from any thread
88        /// without causing conflicts, unexpected behavior.
89        unsafe extern "C" fn free_func(_contents: *mut c_void, free_user_data: *mut c_void) {
90            let raw: *const Box<[u8]> = free_user_data.cast();
91            // SAFETY: `free_func` is called by SM and returns ownership of the Arc we
92            // leaked below with `into_raw`. Hence it is safe to reconstruct the Arc,
93            // and destroy it to release the reference count.
94            drop(unsafe { Arc::from_raw(raw) });
95        }
96        let raw: *const Box<[u8]> = Arc::into_raw(Arc::clone(&self.data));
97        // SAFETY: We leaked the Arc, so the underlying slice will stay alive
98        // until `free_func` is called. `range.start..range.end` is inside
99        // the valid range of the slice.
100        let data_ptr = unsafe { (**raw).as_ptr().add(range.start) };
101        rooted!(&in(cx) let object = unsafe {
102            NewExternalArrayBuffer(
103                cx,
104                range_len,
105                // FIXME(jschwe): I believe casting to a mutable pointer is unsound.
106                // We would need interior mutability.
107                data_ptr.cast_mut().cast(),
108                Some(free_func),
109                raw as _,
110            )
111        });
112        self.data_views.push(DataView {
113            range,
114            buffer: HeapArrayBuffer::from(*object).unwrap(),
115        });
116        Ok(self.data_views.last().unwrap())
117    }
118}
119
120/// DataView are created from `NewExternalArrayBuffer`,
121/// so SM will detach the underlying buffer when the DataView is GCed.
122#[derive(JSTraceable, MallocSizeOf)]
123#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
124pub(crate) struct DataView {
125    #[no_trace]
126    range: Range<usize>,
127    #[ignore_malloc_size_of = "defined in mozjs"]
128    buffer: HeapArrayBuffer,
129}
130
131impl DataView {
132    pub(crate) fn array_buffer(&self) -> RootedTraceableBox<HeapArrayBuffer> {
133        RootedTraceableBox::new(unsafe {
134            HeapArrayBuffer::from(self.buffer.underlying_object().get()).unwrap()
135        })
136    }
137}