nova_vm 1.0.0

Nova Virtual Machine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use ecmascript_atomics::Ordering;

use super::{AnyArrayBuffer, ArrayBuffer, InternalBuffer};
#[cfg(feature = "shared-array-buffer")]
use crate::ecmascript::SharedDataBlock;
use crate::{
    ecmascript::{
        Agent, BUILTIN_STRING_MEMORY, ExceptionType, Function, JsResult, Numeric, Object,
        ProtoIntrinsics, Value, Viewable, create_byte_data_block, get,
        ordinary_create_from_constructor, to_index,
    },
    engine::{Bindable, GcScope, NoGcScope},
    heap::{ArenaAccess, ArenaAccessMut},
};

// TODO: Implement the contents of the `DetachKey` struct?
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct DetachKey {}

/// ### [25.1.3.1 AllocateArrayBuffer ( constructor, byteLength \[ , maxByteLength \] )](https://tc39.es/ecma262/#sec-allocatearraybuffer)
///
/// The abstract operation AllocateArrayBuffer takes arguments *constructor*
/// (a constructor) and *byteLength* (a non-negative integer) and optional
/// argument *maxByteLength* (a non-negative integer or EMPTY) and returns
/// either a normal completion containing an ArrayBuffer or a throw
/// completion. It is used to create an ArrayBuffer.
pub(crate) fn allocate_array_buffer<'a>(
    agent: &mut Agent,
    constructor: Function,
    byte_length: u64,
    max_byte_length: Option<u64>,
    mut gc: GcScope<'a, '_>,
) -> JsResult<'a, ArrayBuffer<'a>> {
    let constructor = constructor.bind(gc.nogc());
    // 1. Let slots be « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] ».
    // 2. If maxByteLength is present and maxByteLength is not EMPTY, let allocatingResizableBuffer be true; otherwise let allocatingResizableBuffer be false.
    // 3. If allocatingResizableBuffer is true, then
    if let Some(max_byte_length) = max_byte_length {
        // a. If byteLength > maxByteLength, throw a RangeError exception.
        if byte_length > max_byte_length {
            return Err(agent.throw_exception_with_static_message(
                ExceptionType::RangeError,
                "Byte length is over maximumm byte length",
                gc.into_nogc(),
            ));
        }
        // b. Append [[ArrayBufferMaxByteLength]] to slots.
    }
    // 4. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", slots).
    let Object::ArrayBuffer(obj) = ordinary_create_from_constructor(
        agent,
        constructor.unbind(),
        ProtoIntrinsics::ArrayBuffer,
        gc.reborrow(),
    )
    .unbind()?
    .bind(gc.nogc()) else {
        unreachable!()
    };
    let obj = obj.unbind();
    let gc = gc.into_nogc();
    let obj = obj.bind(gc);
    // 5. Let block be ? CreateByteDataBlock(byteLength).
    // 8. If allocatingResizableBuffer is true, then
    let buffer = if let Some(max_byte_length) = max_byte_length {
        // a. If it is not possible to create a Data Block block consisting of
        //    maxByteLength bytes, throw a RangeError exception.
        // b. NOTE: Resizable ArrayBuffers are designed to be implementable
        //    with in-place growth. Implementations may throw if, for example,
        //    virtual memory cannot be reserved up front.
        // c. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength.
        let mut block = create_byte_data_block(agent, max_byte_length, gc)?;
        block.realloc(byte_length as usize);
        InternalBuffer::resizable(block, max_byte_length as usize)
    } else {
        InternalBuffer::fixed_length(create_byte_data_block(agent, byte_length, gc)?)
    };
    // 6. Set obj.[[ArrayBufferData]] to block.
    // 7. Set obj.[[ArrayBufferByteLength]] to byteLength.
    obj.get_mut(agent).buffer = buffer;
    // 9. Return obj.
    Ok(obj)
}

/// ### [25.1.3.3 IsDetachedBuffer ( arrayBuffer )](https://tc39.es/ecma262/#sec-isdetachedbuffer)
///
/// The abstract operation IsDetachedBuffer takes argument *arrayBuffer* (an
/// ArrayBuffer or a SharedArrayBuffer) and returns a Boolean.
#[inline]
pub(crate) fn is_detached_buffer(agent: &Agent, array_buffer: ArrayBuffer) -> bool {
    // 1. If arrayBuffer.[[ArrayBufferData]] is null, return true.
    // 2. Return false.
    array_buffer.get(agent).is_detached()
}

/// ### [25.1.3.4 DetachArrayBuffer ( arrayBuffer \[ , key \] )](https://tc39.es/ecma262/#sec-detacharraybuffer)
///
/// The abstract operation DetachArrayBuffer takes argument *arrayBuffer* (an
/// ArrayBuffer) and optional argument *key* (anything) and returns either a
/// normal completion containing UNUSED or a throw completion.
pub(crate) fn detach_array_buffer<'a>(
    agent: &mut Agent,
    array_buffer: ArrayBuffer,
    key: Option<DetachKey>,
    gc: NoGcScope<'a, '_>,
) -> JsResult<'a, ()> {
    // 1. Assert: IsSharedArrayBuffer(arrayBuffer) is false.
    // 2. If key is not present, set key to undefined.
    // 3. If arrayBuffer.[[ArrayBufferDetachKey]] is not key, throw a TypeError exception.
    if array_buffer.get_detach_key(agent) != key {
        return Err(agent.throw_exception_with_static_message(
            ExceptionType::TypeError,
            "Mismatching array buffer detach keys",
            gc,
        ));
    }

    // 4. Set arrayBuffer.[[ArrayBufferData]] to null.
    // 5. Set arrayBuffer.[[ArrayBufferByteLength]] to 0.
    array_buffer.get_mut(agent).buffer.detach();
    // 6. Return UNUSED.
    Ok(())
}

/// ### [25.1.3.6 GetArrayBufferMaxByteLengthOption ( options )](https://tc39.es/ecma262/#sec-getarraybuffermaxbytelengthoption)
///
/// The abstract operation GetArrayBufferMaxByteLengthOption takes argument
/// options (an ECMAScript language value) and returns either a normal
/// completion containing either a non-negative integer or EMPTY, or a throw
/// completion.
pub(crate) fn get_array_buffer_max_byte_length_option<'a>(
    agent: &mut Agent,
    options: Value,
    mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Option<u64>> {
    let options = options.bind(gc.nogc());
    // 1. If options is not an Object, return EMPTY.
    let options = if let Ok(options) = Object::try_from(options) {
        options
    } else {
        return Ok(None);
    };
    // 2. Let maxByteLength be ? Get(options, "maxByteLength").
    let max_byte_length = get(
        agent,
        options.unbind(),
        BUILTIN_STRING_MEMORY.maxByteLength.into(),
        gc.reborrow(),
    )
    .unbind()?
    .bind(gc.nogc());
    // 3. If maxByteLength is undefined, return EMPTY.
    if max_byte_length.is_undefined() {
        return Ok(None);
    }
    // 4. Return ? ToIndex(maxByteLength).
    Ok(Some(to_index(agent, max_byte_length.unbind(), gc)? as u64))
}

/// ### [25.1.3.8 IsFixedLengthArrayBuffer ( arrayBuffer )](https://tc39.es/ecma262/#sec-isfixedlengtharraybuffer)
///
/// The abstract operation IsFixedLengthArrayBuffer takes argument
/// arrayBuffer (an ArrayBuffer or a SharedArrayBuffer) and returns a
/// Boolean.
#[inline(always)]
pub(crate) fn is_fixed_length_array_buffer(agent: &Agent, array_buffer: AnyArrayBuffer) -> bool {
    // 1. If arrayBuffer has an [[ArrayBufferMaxByteLength]] internal slot, return false.
    // 2. Return true.
    !array_buffer.is_resizable(agent)
}

/// ### [25.1.3.13 RawBytesToNumeric ( type, rawBytes, isLittleEndian )](https://tc39.es/ecma262/#sec-rawbytestonumeric)
///
/// The abstract operation RawBytesToNumeric takes arguments type (a
/// TypedArray element type), rawBytes (a List of byte values), and
/// isLittleEndian (a Boolean) and returns a Number or a BigInt.
#[inline(always)]
pub(crate) fn raw_bytes_to_numeric<'a, T: Viewable>(
    agent: &mut Agent,
    raw_bytes: T,
    is_little_endian: bool,
    gc: NoGcScope<'a, '_>,
) -> Numeric<'a> {
    // 1. Let elementSize be the Element Size value specified in Table 71 for Element Type type.
    // 2. If isLittleEndian is false, reverse the order of the elements of rawBytes.
    // 3. If type is FLOAT32, then
    // a. Let value be the byte elements of rawBytes concatenated and interpreted as a little-endian bit string encoding of an IEEE 754-2019 binary32 value.
    // b. If value is an IEEE 754-2019 binary32 NaN value, return the NaN Number value.
    // c. Return the Number value that corresponds to value.
    // 4. If type is FLOAT64, then
    // a. Let value be the byte elements of rawBytes concatenated and interpreted as a little-endian bit string encoding of an IEEE 754-2019 binary64 value.
    // b. If value is an IEEE 754-2019 binary64 NaN value, return the NaN Number value.
    // c. Return the Number value that corresponds to value.
    // 5. If IsUnsignedElementType(type) is true, then
    // a. Let intValue be the byte elements of rawBytes concatenated and interpreted as a bit string encoding of an unsigned little-endian binary number.
    // 6. Else,
    // a. Let intValue be the byte elements of rawBytes concatenated and interpreted as a bit string encoding of a binary little-endian two's complement number of bit length elementSize × 8.
    // 7. If IsBigIntElementType(type) is true, return the BigInt value that corresponds to intValue.
    // 8. Otherwise, return the Number value that corresponds to intValue.
    if is_little_endian {
        raw_bytes.into_le_value(agent, gc)
    } else {
        raw_bytes.into_be_value(agent, gc)
    }
}

/// ### [25.1.3.14 GetRawBytesFromSharedBlock ( block, byteIndex, type, isTypedArray, order )](https://tc39.es/ecma262/#sec-getrawbytesfromsharedblock)
///
/// The abstract operation GetRawBytesFromSharedBlock takes arguments block
/// (a Shared Data Block), byteIndex (a non-negative integer), type (a
/// TypedArray element type), isTypedArray (a Boolean), and order (SEQ-CST
/// or UNORDERED) and returns a List of byte values.
#[inline(always)]
#[cfg(feature = "shared-array-buffer")]
pub(crate) fn get_raw_bytes_from_shared_block<T: Viewable>(
    block: &SharedDataBlock,
    byte_index: usize,
    is_typed_array: bool,
    order: Ordering,
) -> T {
    // 1. Let elementSize be the Element Size value specified in Table 71 for Element Type type.
    // 2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
    // 3. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
    // 4. If isTypedArray is true and IsNoTearConfiguration(type, order) is true, let noTear be true; otherwise let noTear be false.
    // 5. Let rawValue be a List of length elementSize whose elements are nondeterministically chosen byte values.
    // 6. NOTE: In implementations, rawValue is the result of a non-atomic or atomic read instruction on the underlying hardware. The nondeterminism is a semantic prescription of the memory model to describe observable behaviour of hardware with weak consistency.
    // 7. Let readEvent be ReadSharedMemory { [[Order]]: order, [[NoTear]]: noTear, [[Block]]: block, [[ByteIndex]]: byteIndex, [[ElementSize]]: elementSize }.
    // 8. Append readEvent to eventsRecord.[[EventList]].
    // 9. Append Chosen Value Record { [[Event]]: readEvent, [[ChosenValue]]: rawValue } to execution.[[ChosenValues]].
    // 10. Return rawValue.
    if is_typed_array {
        block.load::<T>(byte_index, order).expect("Invalid GetRawBytesFromSharedBlock call: did not check index alignment and buffer length beforehand")
    } else {
        block.load_unaligned::<T>(byte_index).expect(
            "Invalid GetRawBytesFromSharedBlock call: did not check buffer length beforehand",
        )
    }
}

/// ### [25.1.3.15 GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order \[ , isLittleEndian \] )](https://tc39.es/ecma262/#sec-getvaluefrombuffer)
///
/// The abstract operation GetValueFromBuffer takes arguments arrayBuffer
/// (an ArrayBuffer or SharedArrayBuffer), byteIndex (a non-negative
/// integer), type (a TypedArray element type), isTypedArray (a Boolean),
/// and order (SEQ-CST or UNORDERED) and optional argument isLittleEndian
/// (a Boolean) and returns a Number or a BigInt.
pub(crate) fn get_value_from_buffer<'a, T: Viewable>(
    agent: &mut Agent,
    array_buffer: AnyArrayBuffer,
    byte_index: usize,
    is_typed_array: bool,
    order: Ordering,
    is_little_endian: Option<bool>,
    gc: NoGcScope<'a, '_>,
) -> Numeric<'a> {
    // 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
    debug_assert!(!array_buffer.is_detached(agent));
    // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
    // 4. Let elementSize be the Element Size value specified in Table 71 for Element Type type.
    let raw_value = match array_buffer {
        // 5. If IsSharedArrayBuffer(arrayBuffer) is true, then
        #[cfg(feature = "shared-array-buffer")]
        AnyArrayBuffer::SharedArrayBuffer(sab) => {
            // 3. Let block be arrayBuffer.[[ArrayBufferData]].
            let block = sab.get_data_block(agent);
            // a. Assert: block is a Shared Data Block.
            // b. Let rawValue be GetRawBytesFromSharedBlock(block, byteIndex, type,
            //    isTypedArray, order).
            get_raw_bytes_from_shared_block::<T>(block, byte_index, is_typed_array, order)
        }
        // 6. Else,
        AnyArrayBuffer::ArrayBuffer(ab) => {
            let _ = order;
            // 3. Let block be arrayBuffer.[[ArrayBufferData]].
            let block = ab.get(agent).get_data_block();
            // a. Let rawValue be a List whose elements are bytes from block at indices
            //    in the interval from byteIndex (inclusive) to byteIndex + elementSize
            //    (exclusive).
            if is_typed_array {
                // SAFETY: Caller checked.
                unsafe { block.read_aligned(byte_index) }
            } else {
                // SAFETY: Caller checked.
                unsafe { block.read_unaligned(byte_index) }
            }
        }
    };
    // 7. Assert: The number of elements in rawValue is elementSize.
    // 8. If isLittleEndian is not present, set isLittleEndian to the value of
    //    the [[LittleEndian]] field of the surrounding agent's Agent Record.
    let is_little_endian = is_little_endian.unwrap_or({
        #[cfg(target_endian = "little")]
        {
            true
        }
        #[cfg(target_endian = "big")]
        {
            false
        }
    });

    // 9. Return RawBytesToNumeric(type, rawValue, isLittleEndian).
    raw_bytes_to_numeric::<T>(agent, raw_value, is_little_endian, gc)
}

/// ### [25.1.3.16 NumericToRawBytes ( type, value, isLittleEndian )](https://tc39.es/ecma262/#sec-numerictorawbytes)
///
/// The abstract operation NumericToRawBytes takes arguments type (a
/// TypedArray element type), value (a Number or a BigInt), and
/// isLittleEndian (a Boolean) and returns a List of byte values.
pub(crate) fn numeric_to_raw_bytes<T: Viewable>(
    agent: &mut Agent,
    value: Numeric,
    is_little_endian: bool,
) -> T {
    // 1. If type is FLOAT32, then
    // a. Let rawBytes be a List whose elements are the 4 bytes that are the result of converting value to IEEE 754-2019 binary32 format using roundTiesToEven mode. The bytes are arranged in little endian order. If value is NaN, rawBytes may be set to any implementation chosen IEEE 754-2019 binary32 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable NaN value.
    // 2. Else if type is FLOAT64, then
    // a. Let rawBytes be a List whose elements are the 8 bytes that are the IEEE 754-2019 binary64 format encoding of value. The bytes are arranged in little endian order. If value is NaN, rawBytes may be set to any implementation chosen IEEE 754-2019 binary64 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable NaN value.
    // 3. Else,
    // a. Let n be the Element Size value specified in Table 71 for Element Type type.
    // b. Let convOp be the abstract operation named in the Conversion Operation column in Table 71 for Element Type type.
    // c. Let intValue be ℝ(convOp(value)).
    // d. If intValue ≥ 0, then
    // i. Let rawBytes be a List whose elements are the n-byte binary encoding of intValue. The bytes are ordered in little endian order.
    // e. Else,
    // i. Let rawBytes be a List whose elements are the n-byte binary two's complement encoding of intValue. The bytes are ordered in little endian order.
    // 4. If isLittleEndian is false, reverse the order of the elements of rawBytes.
    // 5. Return rawBytes.
    if is_little_endian {
        T::from_le_value(agent, value)
    } else {
        T::from_be_value(agent, value)
    }
}

/// ### [25.1.3.17 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order \[ , isLittleEndian \] )](https://tc39.es/ecma262/#sec-setvalueinbuffer)
///
/// The abstract operation SetValueInBuffer takes arguments arrayBuffer (an
/// ArrayBuffer or SharedArrayBuffer), byteIndex (a non-negative integer),
/// type (a TypedArray element type), value (a Number or a BigInt),
/// isTypedArray (a Boolean), and order (SEQ-CST, UNORDERED, or INIT) and
/// optional argument isLittleEndian (a Boolean) and returns UNUSED.
#[allow(clippy::too_many_arguments)]
pub(crate) fn set_value_in_buffer<T: Viewable>(
    agent: &mut Agent,
    array_buffer: AnyArrayBuffer,
    byte_index: usize,
    value: Numeric,
    is_typed_array: bool,
    order: Ordering,
    is_little_endian: Option<bool>,
) {
    // 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
    debug_assert!(!array_buffer.is_detached(agent));
    // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type.
    // 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; otherwise, value is a Number.

    // 5. Let elementSize be the Element Size value specified in Table 71 for Element Type type.
    // 6. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
    let is_little_endian = is_little_endian.unwrap_or({
        #[cfg(target_endian = "little")]
        {
            true
        }
        #[cfg(target_endian = "big")]
        {
            false
        }
    });

    // 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian).
    let raw_bytes = numeric_to_raw_bytes::<T>(agent, value, is_little_endian);
    match array_buffer {
        // 8. If IsSharedArrayBuffer(arrayBuffer) is true, then
        #[cfg(feature = "shared-array-buffer")]
        AnyArrayBuffer::SharedArrayBuffer(sab) => {
            // 4. Let block be arrayBuffer.[[ArrayBufferData]].
            let block = sab.get_data_block(agent);
            // a. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
            // b. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
            // c. If isTypedArray is true and IsNoTearConfiguration(type, order) is true, let noTear be true; otherwise let noTear be false.
            // d. Append WriteSharedMemory { [[Order]]: order, [[NoTear]]: noTear, [[Block]]: block, [[ByteIndex]]: byteIndex, [[ElementSize]]: elementSize, [[Payload]]: rawBytes } to eventsRecord.[[EventList]].
            if is_typed_array {
                block
                    .store::<T>(byte_index, raw_bytes, order)
                    .expect("Unaligned store or SharedDataBlock not large enough")
            } else {
                block
                    .store_unaligned::<T>(byte_index, raw_bytes)
                    .expect("SharedDataBlock not large enough")
            }
        }
        // 9. Else,
        AnyArrayBuffer::ArrayBuffer(ab) => {
            let _ = order;
            let _ = is_typed_array;
            // 4. Let block be arrayBuffer.[[ArrayBufferData]].
            let block = ab.get_mut(agent).get_data_block_mut();
            // a. Store the individual bytes of rawBytes into block, starting at block[byteIndex].
            block.set_offset_by_byte::<T>(byte_index, raw_bytes);
        }
    }

    // 10. Return UNUSED.
}

/// ### [25.1.3.18 GetModifySetValueInBuffer ( arrayBuffer, byteIndex, type, value, op )](https://tc39.es/ecma262/#sec-getmodifysetvalueinbuffer)
///
/// The abstract operation GetModifySetValueInBuffer takes arguments
/// arrayBuffer (an ArrayBuffer or a SharedArrayBuffer), byteIndex (a
/// non-negative integer), type (a TypedArray element type), value (a Number or
/// a BigInt), and op (a read-modify-write modification function) and returns a
/// Number or a BigInt.
#[cfg(feature = "atomics")]
pub(crate) fn get_modify_set_value_in_buffer<'gc, Type: Viewable, const OP: u8>(
    agent: &mut Agent,
    array_buffer: AnyArrayBuffer,
    byte_index: usize,
    value: Numeric,
    gc: NoGcScope<'gc, '_>,
) -> Numeric<'gc> {
    // 5. Let elementSize be the Element Size value specified in Table
    //    71 for Element Type type.
    let element_size = size_of::<Type>();

    // 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
    debug_assert!(!array_buffer.is_detached(agent));
    // 2. Assert: There are sufficient bytes in arrayBuffer starting at
    //    byteIndex to represent a value of type.
    debug_assert!(
        byte_index + size_of::<Type>() <= array_buffer.byte_length(agent, Ordering::Unordered)
    );
    // 3. Assert: value is a BigInt if IsBigIntElementType(type) is true;
    //    otherwise, value is a Number.
    debug_assert!(value.is_bigint() == Type::IS_BIGINT);
    // 6. Let isLittleEndian be the value of the [[LittleEndian]] field
    //    of the surrounding agent's Agent Record.
    // 7. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian).
    let raw_bytes = Type::from_ne_value(agent, value);
    let raw_bytes_read = match array_buffer {
        AnyArrayBuffer::ArrayBuffer(array_buffer) => {
            let op = get_array_buffer_op::<Type, OP>();
            // 4. Let block be arrayBuffer.[[ArrayBufferData]].
            let block = array_buffer.as_mut_slice(agent);
            // 8. If IsSharedArrayBuffer(arrayBuffer) is true, then
            // 9. Else,
            let slot = &mut block[byte_index..byte_index + element_size];
            // SAFETY: Viewable types are safe to transmute.
            let (head, slot, tail) = unsafe { slot.align_to_mut::<Type>() };
            debug_assert!(head.is_empty() && tail.is_empty());
            // a. Let rawBytesRead be a List of length elementSize whose
            //    elements are the sequence of elementSize bytes starting with
            //    block[byteIndex].
            let raw_bytes_read = slot[0];
            // b. Let rawBytesModified be op(rawBytesRead, rawBytes).
            let data_modified = op(raw_bytes_read, raw_bytes);
            // c. Store the individual bytes of rawBytesModified into block,
            //    starting at block[byteIndex].
            slot[0] = data_modified;
            raw_bytes_read
        }
        AnyArrayBuffer::SharedArrayBuffer(array_buffer) => {
            // 8. If IsSharedArrayBuffer(arrayBuffer) is true, then
            let buffer = array_buffer.as_slice(agent);
            let slot = buffer.slice(byte_index, byte_index + element_size);
            let (head, slot, tail) = slot.align_to::<Type::Storage>();
            debug_assert!(head.is_empty() && tail.is_empty());
            let slot = slot.get(0).unwrap();
            let result = if const { OP == 0 } {
                // Add
                let raw_bytes = Type::into_storage(raw_bytes);
                slot.fetch_add(raw_bytes)
            } else if const { OP == 1 } {
                // And
                let raw_bytes = Type::into_storage(raw_bytes);
                slot.fetch_and(raw_bytes)
            } else if const { OP == 2 } {
                // Exchange
                let raw_bytes = Type::into_storage(raw_bytes);
                slot.swap(raw_bytes)
            } else if const { OP == 3 } {
                // Or
                let raw_bytes = Type::into_storage(raw_bytes);
                slot.fetch_or(raw_bytes)
            } else if const { OP == 4 } {
                // Sub
                let raw_bytes = raw_bytes.neg();
                let raw_bytes = Type::into_storage(raw_bytes);
                slot.fetch_add(raw_bytes)
            } else if const { OP == 5 } {
                // Xor
                let raw_bytes = Type::into_storage(raw_bytes);
                slot.fetch_xor(raw_bytes)
            } else {
                panic!("Unsupported Op value");
            };
            // a. Let execution be the [[CandidateExecution]] field of the
            //    surrounding agent's Agent Record.
            // b. Let eventsRecord be the Agent Events Record of
            //    execution.[[EventsRecords]] whose [[AgentSignifier]] is
            //    AgentSignifier().
            // c. Let rawBytesRead be a List of length elementSize whose
            //    elements are nondeterministically chosen byte values.
            // d. NOTE: In implementations, rawBytesRead is the result of a
            //    load-link, of a load-exclusive, or of an operand of a
            //    read-modify-write instruction on the underlying hardware. The
            //    nondeterminism is a semantic prescription of the memory model
            //    to describe observable behaviour of hardware with weak
            //    consistency.
            // e. Let rmwEvent be ReadModifyWriteSharedMemory { .. }.
            // f. Append rmwEvent to eventsRecord.[[EventList]].
            // g. Append Chosen Value Record { [[Event]]: rmwEvent,
            //    [[ChosenValue]]: rawBytesRead } to execution.[[ChosenValues]].
            Type::from_storage(result)
        }
    };
    // 10. Return RawBytesToNumeric(type, rawBytesRead, isLittleEndian).
    raw_bytes_read.into_ne_value(agent, gc)
}

#[cfg(feature = "atomics")]
type ModifyOp<T> = fn(T, T) -> T;

#[cfg(feature = "atomics")]
const fn get_array_buffer_op<T: Viewable, const OP: u8>() -> ModifyOp<T> {
    if const { OP == 0 } {
        // Add
        <T as Viewable>::add
    } else if const { OP == 1 } {
        // And
        <T as Viewable>::and
    } else if const { OP == 2 } {
        // Exchange
        <T as Viewable>::swap
    } else if const { OP == 3 } {
        // Or
        <T as Viewable>::or
    } else if const { OP == 4 } {
        // Sub
        <T as Viewable>::sub
    } else if const { OP == 5 } {
        // Xor
        <T as Viewable>::xor
    } else {
        panic!("Unsupported Op value");
    }
}

#[cfg(feature = "atomics")]
pub(crate) fn compare_exchange_in_buffer<'gc, Type: Viewable>(
    agent: &mut Agent,
    array_buffer: AnyArrayBuffer,
    byte_index: usize,
    expected: Numeric,
    replacement: Numeric,
    gc: NoGcScope<'gc, '_>,
) -> Numeric<'gc> {
    // 5. Let elementSize be the Element Size value specified in Table
    //    71 for Element Type type.
    let element_size = size_of::<Type>();

    // 1. Assert: IsDetachedBuffer(arrayBuffer) is false.
    debug_assert!(!array_buffer.is_detached(agent));
    // 2. Assert: There are sufficient bytes in arrayBuffer starting at
    //    byteIndex to represent a value of type.
    debug_assert!(
        byte_index + size_of::<Type>() <= array_buffer.byte_length(agent, Ordering::Unordered)
    );
    // 3. Assert: value is a BigInt if IsBigIntElementType(type) is true;
    //    otherwise, value is a Number.
    debug_assert!(
        expected.is_bigint() == Type::IS_BIGINT && replacement.is_bigint() == Type::IS_BIGINT
    );
    // 10. Let isLittleEndian be AR.[[LittleEndian]].
    // 11. Let expectedBytes be
    //     NumericToRawBytes(elementType, expected, isLittleEndian).
    let expected_bytes = Type::from_ne_value(agent, expected);
    // 12. Let replacementBytes be
    //     NumericToRawBytes(elementType, replacement, isLittleEndian).
    let replacement_bytes = Type::from_ne_value(agent, replacement);
    let raw_bytes_read = match array_buffer {
        // 13. If IsSharedArrayBuffer(buffer) is true, then
        AnyArrayBuffer::SharedArrayBuffer(array_buffer) => {
            let buffer = array_buffer.as_slice(agent);
            let slot = buffer.slice(byte_index, byte_index + element_size);
            let (head, slot, tail) = slot.align_to::<Type::Storage>();
            debug_assert!(head.is_empty() && tail.is_empty());
            let slot = slot.get(0).unwrap();
            // a. Let rawBytesRead be
            //    AtomicCompareExchangeInSharedBlock(block, byteIndexInBuffer,
            //    elementSize, expectedBytes, replacementBytes).
            let expected_bytes = Type::into_storage(expected_bytes);
            let replacement_bytes = Type::into_storage(replacement_bytes);
            match slot.compare_exchange(expected_bytes, replacement_bytes) {
                Ok(r) => Type::from_storage(r),
                Err(r) => Type::from_storage(r),
            }
        }
        // 14. Else,
        AnyArrayBuffer::ArrayBuffer(array_buffer) => {
            // 3. Let block be buffer.[[ArrayBufferData]].
            let block = array_buffer.as_mut_slice(agent);
            let slot = &mut block[byte_index..byte_index + element_size];
            // SAFETY: Viewable types are safe to transmute.
            let (head, slot, tail) = unsafe { slot.align_to_mut::<Type>() };
            debug_assert!(head.is_empty() && tail.is_empty());
            // a. Let rawBytesRead be a List of length elementSize whose
            //    elements are the sequence of elementSize bytes starting with
            //    block[byteIndexInBuffer].
            let raw_bytes_read = slot[0];
            // b. If ByteListEqual(rawBytesRead, expectedBytes) is true, then
            if raw_bytes_read == expected_bytes {
                // i. Store the individual bytes of replacementBytes into
                //    block, starting at block[byteIndexInBuffer].
                slot[0] = replacement_bytes;
            }
            raw_bytes_read
        }
    };
    // 15. Return RawBytesToNumeric(elementType, rawBytesRead, isLittleEndian).
    raw_bytes_read.into_ne_value(agent, gc)
}