Skip to main content

oxide_core/heap/
buffer.rs

1//! Array buffer storage and typed-array views.
2//!
3//! An `ArrayBuffer` owns its bytes; a `SharedArrayBuffer`'s bytes are an
4//! `Arc` of atomic bytes, allocated at the maximum length for a growable
5//! one, so agents on other threads share them in safe Rust and `Atomics`
6//! operates on them directly. A typed array or `DataView` is a [`View`]
7//! over a buffer object held in its internal slot 0.
8
9use std::sync::Arc;
10use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
11
12/// The bytes of a shared buffer, which other agents may hold too.
13#[derive(Debug)]
14pub struct SharedBytes {
15    bytes: Box<[AtomicU8]>,
16    len: AtomicUsize,
17}
18
19impl SharedBytes {
20    pub fn new(len: usize, capacity: usize) -> Arc<SharedBytes> {
21        Arc::new(SharedBytes {
22            bytes: (0..capacity.max(len)).map(|_| AtomicU8::new(0)).collect(),
23            len: AtomicUsize::new(len),
24        })
25    }
26
27    pub fn len(&self) -> usize {
28        self.len.load(Ordering::SeqCst)
29    }
30
31    pub fn is_empty(&self) -> bool {
32        self.len() == 0
33    }
34
35    pub fn capacity(&self) -> usize {
36        self.bytes.len()
37    }
38
39    /// Grows to `len` (at most the capacity); false if another agent made
40    /// it longer meanwhile or `len` is shorter.
41    pub fn grow(&self, len: usize) -> bool {
42        let current = self.len();
43        len >= current
44            && len <= self.capacity()
45            && self
46                .len
47                .compare_exchange(current, len, Ordering::SeqCst, Ordering::SeqCst)
48                .is_ok()
49    }
50
51    pub fn byte(&self, index: usize) -> &AtomicU8 {
52        &self.bytes[index]
53    }
54}
55
56/// Where a buffer's bytes are.
57#[derive(Debug)]
58pub enum Store {
59    Owned(Vec<u8>),
60    Shared(Arc<SharedBytes>),
61}
62
63/// An `ArrayBuffer` or `SharedArrayBuffer` record.
64#[derive(Debug)]
65pub struct Buffer {
66    pub store: Store,
67    pub detached: bool,
68    /// `maxByteLength` of a resizable or growable buffer.
69    pub max: Option<usize>,
70}
71
72impl Buffer {
73    pub fn owned(len: usize, max: Option<usize>) -> Buffer {
74        Buffer {
75            store: Store::Owned(vec![0; len]),
76            detached: false,
77            max,
78        }
79    }
80
81    pub fn shared(len: usize, max: Option<usize>) -> Buffer {
82        Buffer {
83            store: Store::Shared(SharedBytes::new(len, max.unwrap_or(len))),
84            detached: false,
85            max,
86        }
87    }
88
89    pub fn is_shared(&self) -> bool {
90        matches!(self.store, Store::Shared(_))
91    }
92
93    pub fn len(&self) -> usize {
94        match &self.store {
95            Store::Owned(bytes) => bytes.len(),
96            Store::Shared(shared) => shared.len(),
97        }
98    }
99
100    pub fn is_empty(&self) -> bool {
101        self.len() == 0
102    }
103
104    pub fn owned_bytes(&self) -> usize {
105        match &self.store {
106            Store::Owned(bytes) => bytes.capacity(),
107            Store::Shared(shared) => shared.capacity(),
108        }
109    }
110
111    /// Copies `out.len()` bytes from `offset`.
112    #[inline]
113    pub fn read(&self, offset: usize, out: &mut [u8]) {
114        if out.is_empty() {
115            return;
116        }
117        match &self.store {
118            Store::Owned(bytes) => out.copy_from_slice(&bytes[offset..offset + out.len()]),
119            Store::Shared(shared) => {
120                for (index, byte) in out.iter_mut().enumerate() {
121                    *byte = shared.byte(offset + index).load(Ordering::Relaxed);
122                }
123            }
124        }
125    }
126
127    /// Writes `data` at `offset`.
128    #[inline]
129    pub fn write(&mut self, offset: usize, data: &[u8]) {
130        if data.is_empty() {
131            return;
132        }
133        match &mut self.store {
134            Store::Owned(bytes) => bytes[offset..offset + data.len()].copy_from_slice(data),
135            Store::Shared(shared) => {
136                for (index, byte) in data.iter().enumerate() {
137                    shared.byte(offset + index).store(*byte, Ordering::Relaxed);
138                }
139            }
140        }
141    }
142
143    /// Copies `len` bytes within the buffer (`memmove`).
144    pub fn copy_within(&mut self, from: usize, to: usize, len: usize) {
145        if len == 0 {
146            return;
147        }
148        match &mut self.store {
149            Store::Owned(bytes) => bytes.copy_within(from..from + len, to),
150            Store::Shared(_) => {
151                let mut temporary = vec![0; len];
152                self.read(from, &mut temporary);
153                self.write(to, &temporary);
154            }
155        }
156    }
157
158    /// The owned bytes, for bulk work (`None` for a shared buffer).
159    pub fn owned_slice(&self) -> Option<&[u8]> {
160        match &self.store {
161            Store::Owned(bytes) => Some(bytes),
162            Store::Shared(_) => None,
163        }
164    }
165
166    pub fn owned_slice_mut(&mut self) -> Option<&mut Vec<u8>> {
167        match &mut self.store {
168            Store::Owned(bytes) => Some(bytes),
169            Store::Shared(_) => None,
170        }
171    }
172
173    /// The bytes as a copy, for any store.
174    pub fn to_vec(&self, offset: usize, len: usize) -> Vec<u8> {
175        let mut out = vec![0; len];
176        self.read(offset, &mut out);
177        out
178    }
179}
180
181/// A typed array's element type.
182#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
183#[repr(u8)]
184pub enum ElementKind {
185    Int8,
186    Uint8,
187    Uint8Clamped,
188    Int16,
189    Uint16,
190    Int32,
191    Uint32,
192    Float16,
193    Float32,
194    Float64,
195    BigInt64,
196    BigUint64,
197}
198
199impl ElementKind {
200    pub const ALL: [ElementKind; 12] = [
201        ElementKind::Int8,
202        ElementKind::Uint8,
203        ElementKind::Uint8Clamped,
204        ElementKind::Int16,
205        ElementKind::Uint16,
206        ElementKind::Int32,
207        ElementKind::Uint32,
208        ElementKind::Float16,
209        ElementKind::Float32,
210        ElementKind::Float64,
211        ElementKind::BigInt64,
212        ElementKind::BigUint64,
213    ];
214
215    #[inline]
216    pub const fn size(self) -> usize {
217        match self {
218            ElementKind::Int8 | ElementKind::Uint8 | ElementKind::Uint8Clamped => 1,
219            ElementKind::Int16 | ElementKind::Uint16 | ElementKind::Float16 => 2,
220            ElementKind::Int32 | ElementKind::Uint32 | ElementKind::Float32 => 4,
221            ElementKind::Float64 | ElementKind::BigInt64 | ElementKind::BigUint64 => 8,
222        }
223    }
224
225    #[inline]
226    pub const fn is_bigint(self) -> bool {
227        matches!(self, ElementKind::BigInt64 | ElementKind::BigUint64)
228    }
229
230    pub const fn name(self) -> &'static str {
231        match self {
232            ElementKind::Int8 => "Int8Array",
233            ElementKind::Uint8 => "Uint8Array",
234            ElementKind::Uint8Clamped => "Uint8ClampedArray",
235            ElementKind::Int16 => "Int16Array",
236            ElementKind::Uint16 => "Uint16Array",
237            ElementKind::Int32 => "Int32Array",
238            ElementKind::Uint32 => "Uint32Array",
239            ElementKind::Float16 => "Float16Array",
240            ElementKind::Float32 => "Float32Array",
241            ElementKind::Float64 => "Float64Array",
242            ElementKind::BigInt64 => "BigInt64Array",
243            ElementKind::BigUint64 => "BigUint64Array",
244        }
245    }
246
247    pub fn from_index(index: u8) -> ElementKind {
248        ElementKind::ALL[index as usize]
249    }
250}
251
252/// A typed array's or a `DataView`'s window on its buffer.
253#[derive(Clone, Copy, Debug)]
254pub struct View {
255    pub kind: ElementKind,
256    pub byte_offset: usize,
257    /// The length in elements (bytes for a `DataView`); `None` tracks the
258    /// buffer's length.
259    pub length: Option<usize>,
260}
261
262impl View {
263    /// The current length in elements, or `None` when the view is out of
264    /// bounds of a buffer `buffer_len` bytes long (or detached).
265    #[inline]
266    pub fn current_length(
267        &self,
268        buffer_len: usize,
269        detached: bool,
270        element: usize,
271    ) -> Option<usize> {
272        if detached || self.byte_offset > buffer_len {
273            return None;
274        }
275        match self.length {
276            Some(length) => (self.byte_offset + length * element <= buffer_len).then_some(length),
277            None => Some((buffer_len - self.byte_offset) / element),
278        }
279    }
280}
281
282/// QuickJS's limit on a buffer's length (`INT32_MAX`).
283pub const MAX_BUFFER_LENGTH: usize = i32::MAX as usize;
284
285impl super::Heap {
286    /// Charges `extra` bytes about to be allocated outside the arenas to the
287    /// trigger; `keep` survives the safepoint.
288    pub(crate) fn charge(
289        &mut self,
290        extra: usize,
291        keep: &[crate::value::Value],
292    ) -> Result<(), super::HeapError> {
293        self.safepoint(
294            &super::Need {
295                extra,
296                ..Default::default()
297            },
298            keep,
299        )
300    }
301}