import { surroundingAgent } from '../engine.mjs'; import { Type, Value } from '../value.mjs'; import { Q, X, NormalCompletion } from '../completion.mjs'; import { Assert, OrdinaryCreateFromConstructor, IsNonNegativeInteger, CreateByteDataBlock, SameValue, IsConstructor, CopyDataBlockBytes, typedArrayInfoByType, } from './all.mjs'; // #sec-allocatearraybuffer export function AllocateArrayBuffer(constructor, byteLength) { // 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] »). const obj = Q(OrdinaryCreateFromConstructor(constructor, '%ArrayBuffer.prototype%', [ 'ArrayBufferData', 'ArrayBufferByteLength', 'ArrayBufferDetachKey', ])); // 2. Assert: ! IsNonNegativeInteger(byteLength) is true. Assert(X(IsNonNegativeInteger(byteLength)) === Value.true); // 3. Let block be ? CreateByteDataBlock(byteLength). const block = Q(CreateByteDataBlock(byteLength)); // 4. Set obj.[[ArrayBufferData]] to block. obj.ArrayBufferData = block; // 5. Set obj.[[ArrayBufferByteLength]] to byteLength. obj.ArrayBufferByteLength = byteLength; // 6. Return obj. return obj; } // #sec-isdetachedbuffer export function IsDetachedBuffer(arrayBuffer) { // 1. Assert: Type(arrayBuffer) is Object and it has an [[ArrayBufferData]] internal slot. Assert(Type(arrayBuffer) === 'Object' && 'ArrayBufferData' in arrayBuffer); // 2. If arrayBuffer.[[ArrayBufferData]] is null, return true. if (arrayBuffer.ArrayBufferData === Value.null) { return Value.true; } // 3. Return false. return Value.false; } // #sec-detacharraybuffer export function DetachArrayBuffer(arrayBuffer, key) { // 1. Assert: Type(arrayBuffer) is Object and it has [[ArrayBufferData]], [[ArrayBufferByteLength]], and [[ArrayBufferDetachKey]] internal slots. Assert(Type(arrayBuffer) === 'Object' && 'ArrayBufferData' in arrayBuffer && 'ArrayBufferByteLength' in arrayBuffer && 'ArrayBufferDetachKey' in arrayBuffer); // 2. Assert: IsSharedArrayBuffer(arrayBuffer) is false. Assert(IsSharedArrayBuffer(arrayBuffer) === Value.false); // 3. If key is not present, set key to undefined. if (key === undefined) { key = Value.undefined; } // 4. If SameValue(arrayBuffer.[[ArrayBufferDetachKey]], key) is false, throw a TypeError exception. if (SameValue(arrayBuffer.ArrayBufferDetachKey, key) === Value.false) { return surroundingAgent.Throw('TypeError', 'BufferDetachKeyMismatch', key, arrayBuffer); } // 5. Set arrayBuffer.[[ArrayBufferData]] to null. arrayBuffer.ArrayBufferData = Value.null; // 6. Set arrayBuffer.[[ArrayBufferByteLength]] to 0. arrayBuffer.ArrayBufferByteLength = new Value(0); // 7. Return NormalCompletion(null). return NormalCompletion(Value.null); } // #sec-issharedarraybuffer export function IsSharedArrayBuffer(_obj) { return Value.false; } export function CloneArrayBuffer(srcBuffer, srcByteOffset, srcLength, cloneConstructor) { // 1. Assert: Type(srcBuffer) is Object and it has an [[ArrayBufferData]] internal slot. Assert(Type(srcBuffer) === 'Object' && 'ArrayBufferData' in srcBuffer); // 2. Assert: IsConstructor(cloneConstructor) is true. Assert(IsConstructor(cloneConstructor) === Value.true); // 3. Let targetBuffer be ? AllocateArrayBuffer(cloneConstructor, srcLength). const targetBuffer = Q(AllocateArrayBuffer(cloneConstructor, srcLength)); // 4. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. if (IsDetachedBuffer(srcBuffer) === Value.true) { return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached'); } // 5. Let srcBlock be srcBuffer.[[ArrayBufferData]]. const srcBlock = srcBuffer.ArrayBufferData; // 6. Let targetBlock be targetBuffer.[[ArrayBufferData]]. const targetBlock = targetBuffer.ArrayBufferData; // 7. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset.numberValue(), srcLength.numberValue()); // 8. Return targetBuffer. return targetBuffer; } // #sec-isbigintelementtype export function IsBigIntElementType(type) { // 1. If type is BigUint64 or BigInt64, return true. if (type === 'BigUint64' || type === 'BigInt64') { return Value.true; } // 2. Return false return Value.false; } const throwawayBuffer = new ArrayBuffer(8); const throwawayDataView = new DataView(throwawayBuffer); const throwawayArray = new Uint8Array(throwawayBuffer); // #sec-rawbytestonumeric export function RawBytesToNumeric(type, rawBytes, isLittleEndian) { // 1. Let elementSize be the Element Size value specified in Table 61 for Element Type type. const elementSize = typedArrayInfoByType[type].ElementSize; Assert(elementSize === rawBytes.length); const dataViewType = type === 'Uint8C' ? 'Uint8' : type; Object.assign(throwawayArray, rawBytes); return new Value(throwawayDataView[`get${dataViewType}`](0, isLittleEndian === Value.true)); } // #sec-getvaluefrombuffer export function GetValueFromBuffer(arrayBuffer, byteIndex, type, isTypedArray, order, isLittleEndian) { // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. Assert(IsDetachedBuffer(arrayBuffer) === Value.false); // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. // 3. Assert: ! IsNonNegativeInteger(byteIndex) is true. Assert(X(IsNonNegativeInteger(byteIndex)) === Value.true); // 4. Let block be arrayBuffer.[[ArrayBufferData]]. const block = arrayBuffer.ArrayBufferData; // 5. Let elementSize be the Element Size value specified in Table 61 for Element Type type. const elementSize = typedArrayInfoByType[type].ElementSize; // 6. If IsSharedArrayBuffer(arrayBuffer) is true, then if (IsSharedArrayBuffer(arrayBuffer) === Value.true) { Assert(false); } // 7. Else, let rawValue be a List of elementSize containing, in order, the elementSize sequence of bytes starting with block[byteIndex]. const rawValue = [...block.subarray(byteIndex.numberValue(), byteIndex.numberValue() + elementSize)]; // 8. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. if (isLittleEndian === undefined) { isLittleEndian = surroundingAgent.AgentRecord.LittleEndian; } // 9. Return RawBytesToNumeric(type, rawValue, isLittleEndian). return RawBytesToNumeric(type, rawValue, isLittleEndian); } const float32NaNLE = Object.freeze([0, 0, 192, 127]); const float32NaNBE = Object.freeze([127, 192, 0, 0]); const float64NaNLE = Object.freeze([0, 0, 0, 0, 0, 0, 248, 127]); const float64NaNBE = Object.freeze([127, 248, 0, 0, 0, 0, 0, 0]); // #sec-numerictorawbytes export function NumericToRawBytes(type, value, isLittleEndian) { Assert(Type(isLittleEndian) === 'Boolean'); isLittleEndian = isLittleEndian === Value.true; let rawBytes; // One day, we will write our own IEEE 754 and two's complement encoder… if (type === 'Float32') { if (Number.isNaN(value.numberValue())) { rawBytes = isLittleEndian ? [...float32NaNLE] : [...float32NaNBE]; } else { throwawayDataView.setFloat32(0, value.numberValue(), isLittleEndian); rawBytes = [...throwawayArray.subarray(0, 4)]; } } else if (type === 'Float64') { if (Number.isNaN(value.numberValue())) { rawBytes = isLittleEndian ? [...float64NaNLE] : [...float64NaNBE]; } else { throwawayDataView.setFloat64(0, value.numberValue(), isLittleEndian); rawBytes = [...throwawayArray.subarray(0, 8)]; } } else { // a. Let n be the Element Size value specified in Table 61 for Element Type type. const n = typedArrayInfoByType[type].ElementSize; // b. Let convOp be the abstract operation named in the Conversion Operation column in Table 61 for Element Type type. const convOp = typedArrayInfoByType[type].ConversionOperation; // c. Let intValue be convOp(value) treated as a mathematical value, whether the result is a BigInt or Number. const intValue = X(convOp(value)); const dataViewType = type === 'Uint8C' ? 'Uint8' : type; throwawayDataView[`set${dataViewType}`](0, intValue.bigintValue ? intValue.bigintValue() : intValue.numberValue(), isLittleEndian); rawBytes = [...throwawayArray.subarray(0, n)]; } return rawBytes; } // #sec-setvalueinbuffer export function SetValueInBuffer(arrayBuffer, byteIndex, type, value, isTypedArray, order, isLittleEndian) { // 1. Assert: IsDetachedBuffer(arrayBuffer) is false. Assert(IsDetachedBuffer(arrayBuffer) === Value.false); // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. // 3. Assert: ! IsNonNegativeInteger(byteIndex) is true. Assert(X(IsNonNegativeInteger(byteIndex)) === Value.true); // 4. Assert: Type(value) is BigInt if ! IsBigIntElementType(type) is true; otherwise, Type(value) is Number. if (X(IsBigIntElementType(type)) === Value.true) { Assert(Type(value) === 'BigInt'); } else { Assert(Type(value) === 'Number'); } // 5. Let block be arrayBuffer.[[ArrayBufferData]]. const block = arrayBuffer.ArrayBufferData; // 6. Let elementSize be the Element Size value specified in Table 61 for Element Type type. // const elementSize = typedArrayInfo[type].ElementSize; // 7. If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. if (isLittleEndian === undefined) { isLittleEndian = surroundingAgent.AgentRecord.LittleEndian; } // 8. Let rawBytes be NumericToRawBytes(type, value, isLittleEndian). const rawBytes = NumericToRawBytes(type, value, isLittleEndian); // 9. If IsSharedArrayBuffer(arrayBuffer) is true, then if (IsSharedArrayBuffer(arrayBuffer) === Value.true) { Assert(false); } // 10. Else, store the individual bytes of rawBytes into block, in order, starting at block[byteIndex]. rawBytes.forEach((byte, i) => { block[byteIndex.numberValue() + i] = byte; }); // 11. Return NormalCompletion(undefined). return NormalCompletion(Value.undefined); }