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