boa_engine/builtins/array_buffer/
shared.rs

1use std::{
2    ptr,
3    sync::{Arc, atomic::Ordering},
4};
5
6use portable_atomic::{AtomicU8, AtomicUsize};
7
8use boa_gc::{Finalize, Trace};
9
10use crate::{
11    Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue,
12    builtins::{
13        Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
14        array_buffer::{AlignedBox, AlignedVec},
15    },
16    context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
17    js_string,
18    object::internal_methods::get_prototype_from_constructor,
19    property::Attribute,
20    realm::Realm,
21    string::StaticJsStrings,
22};
23
24use super::{get_max_byte_len, utils::copy_shared_to_shared};
25
26/// The internal representation of a `SharedArrayBuffer` object.
27///
28/// This struct implements `Send` and `Sync`, meaning it can be shared between threads
29/// running different JS code at the same time.
30#[derive(Debug, Clone, Trace, Finalize, JsData)]
31pub struct SharedArrayBuffer {
32    // Shared buffers cannot be detached.
33    #[unsafe_ignore_trace]
34    data: Arc<Inner>,
35}
36
37#[derive(Debug)]
38struct Inner {
39    // Technically we should have an `[[ArrayBufferData]]` internal slot,
40    // `[[ArrayBufferByteLengthData]]` and `[[ArrayBufferMaxByteLength]]` slots for growable arrays
41    // or `[[ArrayBufferByteLength]]` for fixed arrays, but we can save some work
42    // by just using this representation instead.
43    //
44    // The maximum buffer length is represented by `buffer.len()`, and `current_len` has the current
45    // buffer length, or `None` if this is a fixed buffer; in this case, `buffer.len()` will be
46    // the true length of the buffer.
47    buffer: AlignedBox<[AtomicU8]>,
48    current_len: Option<AtomicUsize>,
49}
50
51impl Default for Inner {
52    fn default() -> Self {
53        Self {
54            buffer: AlignedVec::new(0).into_boxed_slice(),
55            current_len: None,
56        }
57    }
58}
59
60impl SharedArrayBuffer {
61    /// Creates a `SharedArrayBuffer` with an empty buffer.
62    #[must_use]
63    pub fn empty() -> Self {
64        Self {
65            data: Arc::default(),
66        }
67    }
68
69    /// Gets the length of this `SharedArrayBuffer`.
70    pub(crate) fn len(&self, ordering: Ordering) -> usize {
71        self.data
72            .current_len
73            .as_ref()
74            .map_or_else(|| self.data.buffer.len(), |len| len.load(ordering))
75    }
76
77    /// Gets the inner bytes of this `SharedArrayBuffer`.
78    pub(crate) fn bytes(&self, ordering: Ordering) -> &[AtomicU8] {
79        &self.data.buffer[..self.len(ordering)]
80    }
81
82    /// Gets the inner data of the buffer without accessing the current atomic length.
83    #[track_caller]
84    pub(crate) fn bytes_with_len(&self, len: usize) -> &[AtomicU8] {
85        &self.data.buffer[..len]
86    }
87
88    /// Gets a pointer to the internal shared buffer.
89    pub(crate) fn as_ptr(&self) -> *const AtomicU8 {
90        (*self.data.buffer).as_ptr()
91    }
92
93    pub(crate) fn is_fixed_len(&self) -> bool {
94        self.data.current_len.is_none()
95    }
96}
97
98impl IntrinsicObject for SharedArrayBuffer {
99    fn init(realm: &Realm) {
100        let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
101
102        let get_species = BuiltInBuilder::callable(realm, Self::get_species)
103            .name(js_string!("get [Symbol.species]"))
104            .build();
105
106        let get_byte_length = BuiltInBuilder::callable(realm, Self::get_byte_length)
107            .name(js_string!("get byteLength"))
108            .build();
109
110        let get_growable = BuiltInBuilder::callable(realm, Self::get_growable)
111            .name(js_string!("get growable"))
112            .build();
113
114        let get_max_byte_length = BuiltInBuilder::callable(realm, Self::get_max_byte_length)
115            .name(js_string!("get maxByteLength"))
116            .build();
117
118        BuiltInBuilder::from_standard_constructor::<Self>(realm)
119            .static_accessor(
120                JsSymbol::species(),
121                Some(get_species),
122                None,
123                Attribute::CONFIGURABLE,
124            )
125            .accessor(
126                js_string!("byteLength"),
127                Some(get_byte_length),
128                None,
129                flag_attributes,
130            )
131            .accessor(
132                js_string!("growable"),
133                Some(get_growable),
134                None,
135                flag_attributes,
136            )
137            .accessor(
138                js_string!("maxByteLength"),
139                Some(get_max_byte_length),
140                None,
141                flag_attributes,
142            )
143            .method(Self::slice, js_string!("slice"), 2)
144            .method(Self::grow, js_string!("grow"), 1)
145            .property(
146                JsSymbol::to_string_tag(),
147                Self::NAME,
148                Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
149            )
150            .build();
151    }
152
153    fn get(intrinsics: &Intrinsics) -> JsObject {
154        Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor()
155    }
156}
157
158impl BuiltInObject for SharedArrayBuffer {
159    const NAME: JsString = StaticJsStrings::SHARED_ARRAY_BUFFER;
160}
161
162impl BuiltInConstructor for SharedArrayBuffer {
163    const CONSTRUCTOR_ARGUMENTS: usize = 1;
164    const PROTOTYPE_STORAGE_SLOTS: usize = 9;
165    const CONSTRUCTOR_STORAGE_SLOTS: usize = 2;
166
167    const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
168        StandardConstructors::shared_array_buffer;
169
170    /// `25.1.3.1 SharedArrayBuffer ( length [ , options ] )`
171    ///
172    /// More information:
173    ///  - [ECMAScript reference][spec]
174    ///
175    /// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer-constructor
176    fn constructor(
177        new_target: &JsValue,
178        args: &[JsValue],
179        context: &mut Context,
180    ) -> JsResult<JsValue> {
181        // 1. If NewTarget is undefined, throw a TypeError exception.
182        if new_target.is_undefined() {
183            return Err(JsNativeError::typ()
184                .with_message("ArrayBuffer.constructor called with undefined new target")
185                .into());
186        }
187
188        // 2. Let byteLength be ? ToIndex(length).
189        let byte_len = args.get_or_undefined(0).to_index(context)?;
190
191        // 3. Let requestedMaxByteLength be ? GetArrayBufferMaxByteLengthOption(options).
192        let max_byte_len = get_max_byte_len(args.get_or_undefined(1), context)?;
193
194        // 4. Return ? AllocateSharedArrayBuffer(NewTarget, byteLength, requestedMaxByteLength).
195        Ok(Self::allocate(new_target, byte_len, max_byte_len, context)?
196            .upcast()
197            .into())
198    }
199}
200
201impl SharedArrayBuffer {
202    /// `get SharedArrayBuffer [ @@species ]`
203    ///
204    /// More information:
205    ///  - [ECMAScript reference][spec]
206    ///
207    /// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer-@@species
208    #[allow(clippy::unnecessary_wraps)]
209    fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
210        // 1. Return the this value.
211        Ok(this.clone())
212    }
213
214    /// `get SharedArrayBuffer.prototype.byteLength`
215    ///
216    /// More information:
217    ///  - [ECMAScript reference][spec]
218    ///
219    /// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.bytelength
220    pub(crate) fn get_byte_length(
221        this: &JsValue,
222        _args: &[JsValue],
223        _: &mut Context,
224    ) -> JsResult<JsValue> {
225        // 1. Let O be the this value.
226        // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
227        // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
228        let object = this.as_object();
229        let buf = object
230            .as_ref()
231            .and_then(JsObject::downcast_ref::<Self>)
232            .ok_or_else(|| {
233                JsNativeError::typ()
234                    .with_message("SharedArrayBuffer.byteLength called with invalid value")
235            })?;
236
237        // 4. Let length be ArrayBufferByteLength(O, seq-cst).
238        let len = buf.bytes(Ordering::SeqCst).len() as u64;
239
240        // 5. Return 𝔽(length).
241        Ok(len.into())
242    }
243
244    /// [`get SharedArrayBuffer.prototype.growable`][spec].
245    ///
246    /// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.growable
247    pub(crate) fn get_growable(
248        this: &JsValue,
249        _args: &[JsValue],
250        _context: &mut Context,
251    ) -> JsResult<JsValue> {
252        // 1. Let O be the this value.
253        // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
254        // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception.
255        let object = this.as_object();
256        let buf = object
257            .as_ref()
258            .and_then(JsObject::downcast_ref::<Self>)
259            .ok_or_else(|| {
260                JsNativeError::typ()
261                    .with_message("get SharedArrayBuffer.growable called with invalid `this`")
262            })?;
263
264        // 4. If IsFixedLengthArrayBuffer(O) is false, return true; otherwise return false.
265        Ok(JsValue::from(!buf.is_fixed_len()))
266    }
267
268    /// [`get SharedArrayBuffer.prototype.maxByteLength`][spec].
269    ///
270    /// [spec]: https://tc39.es/ecma262/#sec-get-sharedarraybuffer.prototype.maxbytelength
271    pub(crate) fn get_max_byte_length(
272        this: &JsValue,
273        _args: &[JsValue],
274        _context: &mut Context,
275    ) -> JsResult<JsValue> {
276        // 1. Let O be the this value.
277        // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
278        // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception.
279        let object = this.as_object();
280        let buf = object
281            .as_ref()
282            .and_then(JsObject::downcast_ref::<Self>)
283            .ok_or_else(|| {
284                JsNativeError::typ()
285                    .with_message("get SharedArrayBuffer.maxByteLength called with invalid value")
286            })?;
287
288        // 4. If IsFixedLengthArrayBuffer(O) is true, then
289        //     a. Let length be O.[[ArrayBufferByteLength]].
290        // 5. Else,
291        //     a. Let length be O.[[ArrayBufferMaxByteLength]].
292        // 6. Return 𝔽(length).
293        Ok(buf.data.buffer.len().into())
294    }
295
296    /// [`SharedArrayBuffer.prototype.grow ( newLength )`][spec].
297    ///
298    /// [spec]: https://tc39.es/ecma262/sec-sharedarraybuffer.prototype.grow
299    pub(crate) fn grow(
300        this: &JsValue,
301        args: &[JsValue],
302        context: &mut Context,
303    ) -> JsResult<JsValue> {
304        // 1. Let O be the this value.
305        // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception.
306        let Some(buf) = this
307            .as_object()
308            .and_then(|o| o.clone().downcast::<Self>().ok())
309        else {
310            return Err(JsNativeError::typ()
311                .with_message("SharedArrayBuffer.grow called with non-object value")
312                .into());
313        };
314
315        // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferMaxByteLength]]).
316        if buf.borrow().data().is_fixed_len() {
317            return Err(JsNativeError::typ()
318                .with_message("SharedArrayBuffer.grow: cannot grow a fixed-length buffer")
319                .into());
320        }
321
322        // 4. Let newByteLength be ? ToIndex(newLength).
323        let new_byte_len = args.get_or_undefined(0).to_index(context)?;
324
325        // TODO: 5. Let hostHandled be ? HostGrowSharedArrayBuffer(O, newByteLength).
326        // 6. If hostHandled is handled, return undefined.
327        // Used in engines to handle WASM buffers in a special way, but we don't
328        // have a WASM interpreter in place yet.
329
330        // 7. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
331        // 8. Let byteLengthBlock be O.[[ArrayBufferByteLengthData]].
332        // 9. Let currentByteLengthRawBytes be GetRawBytesFromSharedBlock(byteLengthBlock, 0, biguint64, true, seq-cst).
333        // 10. Let newByteLengthRawBytes be NumericToRawBytes(biguint64, ℤ(newByteLength), isLittleEndian).
334
335        let buf = buf.borrow();
336        let buf = &buf.data();
337
338        // d. If newByteLength < currentByteLength or newByteLength > O.[[ArrayBufferMaxByteLength]], throw a RangeError exception.
339        // Extracting this condition outside the CAS since throwing early doesn't affect the correct
340        // behaviour of the loop.
341        if new_byte_len > buf.data.buffer.len() as u64 {
342            return Err(JsNativeError::range()
343                .with_message(
344                    "SharedArrayBuffer.grow: new length cannot be bigger than `maxByteLength`",
345                )
346                .into());
347        }
348        let new_byte_len = new_byte_len as usize;
349
350        // If we used let-else above to avoid the expect, we would carry a borrow through the `to_index`
351        // call, which could mutably borrow. Another alternative would be to clone the whole
352        // `SharedArrayBuffer`, but it's better to avoid contention with the counter in the `Arc` pointer.
353        let atomic_len = buf
354            .data
355            .current_len
356            .as_ref()
357            .expect("already checked that the buffer is not fixed-length");
358
359        // 11. Repeat,
360        //     a. NOTE: This is a compare-and-exchange loop to ensure that parallel, racing grows of the same buffer are
361        //        totally ordered, are not lost, and do not silently do nothing. The loop exits if it was able to attempt
362        //        to grow uncontended.
363        //     b. Let currentByteLength be ℝ(RawBytesToNumeric(biguint64, currentByteLengthRawBytes, isLittleEndian)).
364        //     c. If newByteLength = currentByteLength, return undefined.
365        //     d. If newByteLength < currentByteLength or newByteLength > O.[[ArrayBufferMaxByteLength]], throw a
366        //        RangeError exception.
367        //     e. Let byteLengthDelta be newByteLength - currentByteLength.
368        //     f. If it is impossible to create a new Shared Data Block value consisting of byteLengthDelta bytes, throw
369        //        a RangeError exception.
370        //     g. NOTE: No new Shared Data Block is constructed and used here. The observable behaviour of growable
371        //        SharedArrayBuffers is specified by allocating a max-sized Shared Data Block at construction time, and
372        //        this step captures the requirement that implementations that run out of memory must throw a RangeError.
373        //     h. Let readByteLengthRawBytes be AtomicCompareExchangeInSharedBlock(byteLengthBlock, 0, 8,
374        //        currentByteLengthRawBytes, newByteLengthRawBytes).
375        //     i. If ByteListEqual(readByteLengthRawBytes, currentByteLengthRawBytes) is true, return undefined.
376        //     j. Set currentByteLengthRawBytes to readByteLengthRawBytes.
377
378        // We require SEQ-CST operations because readers of the buffer also use SEQ-CST operations.
379        atomic_len
380            .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev_byte_len| {
381                (prev_byte_len <= new_byte_len).then_some(new_byte_len)
382            })
383            .map_err(|_| {
384                JsNativeError::range()
385                    .with_message("SharedArrayBuffer.grow: failed to grow buffer to new length")
386            })?;
387
388        Ok(JsValue::undefined())
389    }
390
391    /// `SharedArrayBuffer.prototype.slice ( start, end )`
392    ///
393    /// More information:
394    ///  - [ECMAScript reference][spec]
395    ///
396    /// [spec]: https://tc39.es/ecma262/#sec-sharedarraybuffer.prototype.slice
397    fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
398        // 1. Let O be the this value.
399        // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
400        // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception.
401        let buf = this
402            .as_object()
403            .and_then(|o| o.clone().downcast::<Self>().ok())
404            .ok_or_else(|| {
405                JsNativeError::typ()
406                    .with_message("SharedArrayBuffer.slice called with invalid `this` value")
407            })?;
408
409        // 4. Let len be ArrayBufferByteLength(O, seq-cst).
410        let len = buf.borrow().data().len(Ordering::SeqCst);
411
412        // 5. Let relativeStart be ? ToIntegerOrInfinity(start).
413        // 6. If relativeStart = -∞, let first be 0.
414        // 7. Else if relativeStart < 0, let first be max(len + relativeStart, 0).
415        // 8. Else, let first be min(relativeStart, len).
416        let first = Array::get_relative_start(context, args.get_or_undefined(0), len as u64)?;
417
418        // 9. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
419        // 10. If relativeEnd = -∞, let final be 0.
420        // 11. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0).
421        // 12. Else, let final be min(relativeEnd, len).
422        let final_ = Array::get_relative_end(context, args.get_or_undefined(1), len as u64)?;
423
424        // 13. Let newLen be max(final - first, 0).
425        let new_len = final_.saturating_sub(first);
426
427        // 14. Let ctor be ? SpeciesConstructor(O, %SharedArrayBuffer%).
428        let ctor = buf
429            .clone()
430            .upcast()
431            .species_constructor(StandardConstructors::shared_array_buffer, context)?;
432
433        // 15. Let new be ? Construct(ctor, « 𝔽(newLen) »).
434        let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?;
435
436        {
437            let buf = buf.borrow();
438            let buf = &buf.data();
439            // 16. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]).
440            // 17. If IsSharedArrayBuffer(new) is false, throw a TypeError exception.
441            let new = new.downcast_ref::<Self>().ok_or_else(|| {
442                JsNativeError::typ()
443                    .with_message("SharedArrayBuffer constructor returned invalid object")
444            })?;
445
446            // 18. If new.[[ArrayBufferData]] is O.[[ArrayBufferData]], throw a TypeError exception.
447            if ptr::eq(buf.as_ptr(), new.as_ptr()) {
448                return Err(JsNativeError::typ()
449                    .with_message("cannot reuse the same SharedArrayBuffer for a slice operation")
450                    .into());
451            }
452
453            // 19. If ArrayBufferByteLength(new, seq-cst) < newLen, throw a TypeError exception.
454            if (new.len(Ordering::SeqCst) as u64) < new_len {
455                return Err(JsNativeError::typ()
456                    .with_message("invalid size of constructed SharedArrayBuffer")
457                    .into());
458            }
459
460            let first = first as usize;
461            let new_len = new_len as usize;
462
463            // 20. Let fromBuf be O.[[ArrayBufferData]].
464            let from_buf = &buf.bytes_with_len(len)[first..];
465
466            // 21. Let toBuf be new.[[ArrayBufferData]].
467            let to_buf = new;
468
469            // Sanity check to ensure there is enough space inside `from_buf` for
470            // `new_len` elements.
471            debug_assert!(from_buf.len() >= new_len);
472
473            // 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
474            // SAFETY: `get_slice_range` will always return indices that are in-bounds.
475            // This also means that the newly created buffer will have at least `new_len` elements
476            // to write to.
477            unsafe { copy_shared_to_shared(from_buf.as_ptr(), to_buf.as_ptr(), new_len) }
478        }
479
480        // 23. Return new.
481        Ok(new.into())
482    }
483
484    /// `AllocateSharedArrayBuffer ( constructor, byteLength [ , maxByteLength ] )`
485    ///
486    /// More information:
487    ///  - [ECMAScript reference][spec]
488    ///
489    /// [spec]: https://tc39.es/ecma262/#sec-allocatesharedarraybuffer
490    pub(crate) fn allocate(
491        constructor: &JsValue,
492        byte_len: u64,
493        max_byte_len: Option<u64>,
494        context: &mut Context,
495    ) -> JsResult<JsObject<SharedArrayBuffer>> {
496        // 1. Let slots be « [[ArrayBufferData]] ».
497        // 2. If maxByteLength is present and maxByteLength is not empty, let allocatingGrowableBuffer
498        //    be true; otherwise let allocatingGrowableBuffer be false.
499        // 3. If allocatingGrowableBuffer is true, then
500        //     a. If byteLength > maxByteLength, throw a RangeError exception.
501        //     b. Append [[ArrayBufferByteLengthData]] and [[ArrayBufferMaxByteLength]] to slots.
502        // 4. Else,
503        //     a. Append [[ArrayBufferByteLength]] to slots.
504        if let Some(max_byte_len) = max_byte_len
505            && byte_len > max_byte_len
506        {
507            return Err(JsNativeError::range()
508                .with_message("`length` cannot be bigger than `maxByteLength`")
509                .into());
510        }
511
512        // 5. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%SharedArrayBuffer.prototype%", slots).
513        let prototype = get_prototype_from_constructor(
514            constructor,
515            StandardConstructors::shared_array_buffer,
516            context,
517        )?;
518
519        // 6. If allocatingGrowableBuffer is true, let allocLength be maxByteLength;
520        //    otherwise let allocLength be byteLength.
521        let alloc_len = max_byte_len.unwrap_or(byte_len);
522
523        // 7. Let block be ? CreateSharedByteDataBlock(allocLength).
524        // 8. Set obj.[[ArrayBufferData]] to block.
525        let block = create_shared_byte_data_block(alloc_len, context)?;
526
527        // 9. If allocatingGrowableBuffer is true, then
528        // `byte_len` must fit inside an `usize` thanks to the checks inside
529        // `create_shared_byte_data_block`.
530        // a. Assert: byteLength ≤ maxByteLength.
531        // b. Let byteLengthBlock be ? CreateSharedByteDataBlock(8).
532        // c. Perform SetValueInBuffer(byteLengthBlock, 0, biguint64, ℤ(byteLength), true, seq-cst).
533        // d. Set obj.[[ArrayBufferByteLengthData]] to byteLengthBlock.
534        // e. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength.
535        let current_len = max_byte_len.map(|_| AtomicUsize::new(byte_len as usize));
536
537        // 10. Else,
538        //     a. Set obj.[[ArrayBufferByteLength]] to byteLength.
539        let obj = JsObject::new(
540            context.root_shape(),
541            prototype,
542            Self {
543                data: Arc::new(Inner {
544                    buffer: block,
545                    current_len,
546                }),
547            },
548        );
549
550        // 11. Return obj.
551        Ok(obj)
552    }
553}
554
555/// [`CreateSharedByteDataBlock ( size )`][spec] abstract operation.
556///
557/// Creates a new `Arc<Vec<AtomicU8>>` that can be used as a backing buffer for a [`SharedArrayBuffer`].
558///
559/// For more information, check the [spec][spec].
560///
561/// [spec]: https://tc39.es/ecma262/#sec-createsharedbytedatablock
562pub(crate) fn create_shared_byte_data_block(
563    size: u64,
564    context: &mut Context,
565) -> JsResult<AlignedBox<[AtomicU8]>> {
566    if size > context.host_hooks().max_buffer_size(context) {
567        return Err(JsNativeError::range()
568            .with_message(
569                "cannot allocate a buffer that exceeds the maximum buffer size".to_string(),
570            )
571            .into());
572    }
573
574    // 1. Let db be a new Shared Data Block value consisting of size bytes. If it is impossible to
575    //    create such a Shared Data Block, throw a RangeError exception.
576    let size = size.try_into().map_err(|e| {
577        JsNativeError::range().with_message(format!("couldn't allocate the data block: {e}"))
578    })?;
579
580    if size == 0 {
581        // Must ensure we don't allocate a zero-sized buffer.
582        return Ok(AlignedVec::new(0).into_boxed_slice());
583    }
584
585    // 2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
586    // 3. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose
587    //    [[AgentSignifier]] is AgentSignifier().
588    // 4. Let zero be « 0 ».
589    // 5. For each index i of db, do
590    //     a. Append WriteSharedMemory { [[Order]]: init, [[NoTear]]: true, [[Block]]: db,
591    //        [[ByteIndex]]: i, [[ElementSize]]: 1, [[Payload]]: zero } to eventsRecord.[[EventList]].
592    // 6. Return db.
593    let mut buf = AlignedVec::<u8>::new(0);
594    buf.try_reserve_exact(size).map_err(|e| {
595        let message = match e {
596            aligned_vec::TryReserveError::CapacityOverflow => {
597                format!("capacity overflow for size {size} while allocating data block")
598            }
599            aligned_vec::TryReserveError::AllocError { layout } => {
600                format!("invalid layout {layout:?} while allocating data block")
601            }
602        };
603        JsNativeError::range().with_message(message)
604    })?;
605    buf.resize(size, 0);
606    buf.shrink_to_fit();
607    let (data, align, len, _) = buf.into_raw_parts();
608
609    // 3. Return db.
610    // SAFETY: `[u8]` must be transparently castable to `[AtomicU8]`.
611    Ok(unsafe {
612        AlignedBox::from_raw_parts(align, ptr::slice_from_raw_parts_mut(data.cast(), len))
613    })
614}