JsArrayBuffer.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. using Jint.Native.ArrayBuffer;
  2. using Jint.Native.Object;
  3. using Jint.Native.TypedArray;
  4. using Jint.Runtime;
  5. namespace Jint.Native;
  6. /// <summary>
  7. /// https://tc39.es/ecma262/#sec-arraybuffer-objects
  8. /// </summary>
  9. public class JsArrayBuffer : ObjectInstance
  10. {
  11. // so that we don't need to allocate while or reading setting values
  12. private readonly byte[] _workBuffer = new byte[8];
  13. internal byte[]? _arrayBufferData;
  14. internal readonly int? _arrayBufferMaxByteLength;
  15. internal readonly JsValue _arrayBufferDetachKey = Undefined;
  16. internal JsArrayBuffer(
  17. Engine engine,
  18. byte[] data,
  19. uint? arrayBufferMaxByteLength = null) : base(engine)
  20. {
  21. if (arrayBufferMaxByteLength is > int.MaxValue)
  22. {
  23. ExceptionHelper.ThrowRangeError(engine.Realm, "arrayBufferMaxByteLength cannot be larger than int32.MaxValue");
  24. }
  25. _arrayBufferData = data;
  26. _arrayBufferMaxByteLength = (int?) arrayBufferMaxByteLength;
  27. }
  28. internal static byte[] CreateByteDataBlock(Realm realm, ulong byteLength)
  29. {
  30. if (byteLength > int.MaxValue)
  31. {
  32. ExceptionHelper.ThrowRangeError(realm, "Array buffer allocation failed");
  33. }
  34. return new byte[byteLength];
  35. }
  36. internal virtual int ArrayBufferByteLength => _arrayBufferData?.Length ?? 0;
  37. internal byte[]? ArrayBufferData => _arrayBufferData;
  38. internal bool IsDetachedBuffer => _arrayBufferData is null;
  39. internal bool IsFixedLengthArrayBuffer => _arrayBufferMaxByteLength is null;
  40. internal virtual bool IsSharedArrayBuffer => false;
  41. /// <summary>
  42. /// https://tc39.es/ecma262/#sec-detacharraybuffer
  43. /// </summary>
  44. internal void DetachArrayBuffer(JsValue? key = null)
  45. {
  46. key ??= Undefined;
  47. if (!SameValue(_arrayBufferDetachKey, key))
  48. {
  49. ExceptionHelper.ThrowTypeError(_engine.Realm);
  50. }
  51. _arrayBufferData = null;
  52. }
  53. /// <summary>
  54. /// https://tc39.es/ecma262/#sec-clonearraybuffer
  55. /// </summary>
  56. internal JsArrayBuffer CloneArrayBuffer(
  57. ArrayBufferConstructor constructor,
  58. int srcByteOffset,
  59. uint srcLength)
  60. {
  61. var targetBuffer = constructor.AllocateArrayBuffer(_engine.Realm.Intrinsics.ArrayBuffer, srcLength);
  62. AssertNotDetached();
  63. var srcBlock = _arrayBufferData!;
  64. var targetBlock = targetBuffer.ArrayBufferData!;
  65. // TODO SharedArrayBuffer would use this
  66. //CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength).
  67. System.Array.Copy(srcBlock, srcByteOffset, targetBlock, 0, srcLength);
  68. return targetBuffer;
  69. }
  70. /// <summary>
  71. /// https://tc39.es/ecma262/#sec-getvaluefrombuffer
  72. /// </summary>
  73. internal TypedArrayValue GetValueFromBuffer(
  74. int byteIndex,
  75. TypedArrayElementType type,
  76. bool isTypedArray,
  77. ArrayBufferOrder order,
  78. bool? isLittleEndian = null)
  79. {
  80. return RawBytesToNumeric(type, byteIndex, isLittleEndian ?? BitConverter.IsLittleEndian);
  81. }
  82. /// <summary>
  83. /// https://tc39.es/ecma262/#sec-rawbytestonumeric
  84. /// </summary>
  85. internal TypedArrayValue RawBytesToNumeric(TypedArrayElementType type, int byteIndex, bool isLittleEndian)
  86. {
  87. if (type is TypedArrayElementType.Uint8 or TypedArrayElementType.Uint8C)
  88. {
  89. return new TypedArrayValue(Types.Number, _arrayBufferData![byteIndex], default);
  90. }
  91. var elementSize = type.GetElementSize();
  92. var rawBytes = _arrayBufferData!;
  93. // 8 byte values require a little more at the moment
  94. var needsReverse = !isLittleEndian
  95. && elementSize > 1
  96. && type is TypedArrayElementType.Float16 or TypedArrayElementType.Float32 or TypedArrayElementType.Float64 or TypedArrayElementType.BigInt64 or TypedArrayElementType.BigUint64;
  97. if (needsReverse)
  98. {
  99. System.Array.Copy(rawBytes, byteIndex, _workBuffer, 0, elementSize);
  100. byteIndex = 0;
  101. System.Array.Reverse(_workBuffer, 0, elementSize);
  102. rawBytes = _workBuffer;
  103. }
  104. if (type == TypedArrayElementType.Float16)
  105. {
  106. #if SUPPORTS_HALF
  107. // rawBytes concatenated and interpreted as a little-endian bit string encoding of an IEEE 754-2019 binary32 value.
  108. var value = BitConverter.ToHalf(rawBytes, byteIndex);
  109. // If value is an IEEE 754-2019 binary32 NaN value, return the NaN Number value.
  110. if (Half.IsNaN(value))
  111. {
  112. return double.NaN;
  113. }
  114. return value;
  115. #else
  116. ExceptionHelper.ThrowNotImplementedException("Float16/Half type is not supported in this build");
  117. return default;
  118. #endif
  119. }
  120. if (type == TypedArrayElementType.Float32)
  121. {
  122. // rawBytes concatenated and interpreted as a little-endian bit string encoding of an IEEE 754-2019 binary32 value.
  123. var value = BitConverter.ToSingle(rawBytes, byteIndex);
  124. // If value is an IEEE 754-2019 binary32 NaN value, return the NaN Number value.
  125. if (float.IsNaN(value))
  126. {
  127. return double.NaN;
  128. }
  129. return value;
  130. }
  131. if (type == TypedArrayElementType.Float64)
  132. {
  133. // rawBytes concatenated and interpreted as a little-endian bit string encoding of an IEEE 754-2019 binary64 value.
  134. var value = BitConverter.ToDouble(rawBytes, byteIndex);
  135. return value;
  136. }
  137. if (type == TypedArrayElementType.BigUint64)
  138. {
  139. var value = BitConverter.ToUInt64(rawBytes, byteIndex);
  140. return value;
  141. }
  142. if (type == TypedArrayElementType.BigInt64)
  143. {
  144. var value = BitConverter.ToInt64(rawBytes, byteIndex);
  145. return value;
  146. }
  147. TypedArrayValue? arrayValue = type switch
  148. {
  149. TypedArrayElementType.Int8 => (sbyte) rawBytes[byteIndex],
  150. TypedArrayElementType.Int16 => isLittleEndian
  151. ? (short) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8))
  152. : (short) (rawBytes[byteIndex + 1] | (rawBytes[byteIndex] << 8)),
  153. TypedArrayElementType.Uint16 => isLittleEndian
  154. ? (ushort) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8))
  155. : (ushort) (rawBytes[byteIndex + 1] | (rawBytes[byteIndex] << 8)),
  156. TypedArrayElementType.Int32 => isLittleEndian
  157. ? rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8) | (rawBytes[byteIndex + 2] << 16) | (rawBytes[byteIndex + 3] << 24)
  158. : rawBytes[byteIndex + 3] | (rawBytes[byteIndex + 2] << 8) | (rawBytes[byteIndex + 1] << 16) | (rawBytes[byteIndex + 0] << 24),
  159. TypedArrayElementType.Uint32 => isLittleEndian
  160. ? (uint) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8) | (rawBytes[byteIndex + 2] << 16) | (rawBytes[byteIndex + 3] << 24))
  161. : (uint) (rawBytes[byteIndex + 3] | (rawBytes[byteIndex + 2] << 8) | (rawBytes[byteIndex + 1] << 16) | (rawBytes[byteIndex] << 24)),
  162. _ => null
  163. };
  164. if (arrayValue is null)
  165. {
  166. ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(type), type.ToString());
  167. }
  168. return arrayValue.Value;
  169. }
  170. /// <summary>
  171. /// https://tc39.es/ecma262/#sec-setvalueinbuffer
  172. /// </summary>
  173. internal void SetValueInBuffer(
  174. int byteIndex,
  175. TypedArrayElementType type,
  176. TypedArrayValue value,
  177. bool isTypedArray,
  178. ArrayBufferOrder order,
  179. bool? isLittleEndian = null)
  180. {
  181. if (type is TypedArrayElementType.Uint8)
  182. {
  183. var doubleValue = value.DoubleValue;
  184. var intValue = double.IsNaN(doubleValue) || doubleValue == 0 || double.IsInfinity(doubleValue)
  185. ? 0
  186. : (long) doubleValue;
  187. _arrayBufferData![byteIndex] = (byte) intValue;
  188. return;
  189. }
  190. var block = _arrayBufferData!;
  191. // If isLittleEndian is not present, set isLittleEndian to the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
  192. var rawBytes = NumericToRawBytes(type, value, isLittleEndian ?? BitConverter.IsLittleEndian);
  193. System.Array.Copy(rawBytes, 0, block, byteIndex, type.GetElementSize());
  194. }
  195. private byte[] NumericToRawBytes(TypedArrayElementType type, TypedArrayValue value, bool isLittleEndian)
  196. {
  197. byte[] rawBytes;
  198. if (type == TypedArrayElementType.Float16)
  199. {
  200. #if SUPPORTS_HALF
  201. rawBytes = BitConverter.GetBytes((Half) value.DoubleValue);
  202. #else
  203. ExceptionHelper.ThrowNotImplementedException("Float16/Half type is not supported in this build");
  204. return default!;
  205. #endif
  206. }
  207. else if (type == TypedArrayElementType.Float32)
  208. {
  209. // 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. If isLittleEndian is false, the bytes are arranged in big endian order. Otherwise, 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.
  210. rawBytes = BitConverter.GetBytes((float) value.DoubleValue);
  211. }
  212. else if (type == TypedArrayElementType.Float64)
  213. {
  214. // Let rawBytes be a List whose elements are the 8 bytes that are the IEEE 754-2019 binary64 format encoding of value. If isLittleEndian is false, the bytes are arranged in big endian order. Otherwise, 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.
  215. rawBytes = BitConverter.GetBytes(value.DoubleValue);
  216. }
  217. else if (type == TypedArrayElementType.BigInt64)
  218. {
  219. rawBytes = BitConverter.GetBytes(TypeConverter.ToBigInt64(value.BigInteger));
  220. }
  221. else if (type == TypedArrayElementType.BigUint64)
  222. {
  223. rawBytes = BitConverter.GetBytes(TypeConverter.ToBigUint64(value.BigInteger));
  224. }
  225. else
  226. {
  227. // inlined conversion for faster speed instead of getting the method in spec
  228. var doubleValue = value.DoubleValue;
  229. var intValue = double.IsNaN(doubleValue) || doubleValue == 0 || double.IsInfinity(doubleValue)
  230. ? 0
  231. : (long) doubleValue;
  232. rawBytes = _workBuffer;
  233. switch (type)
  234. {
  235. case TypedArrayElementType.Int8:
  236. rawBytes[0] = (byte) (sbyte) intValue;
  237. break;
  238. case TypedArrayElementType.Uint8:
  239. rawBytes[0] = (byte) intValue;
  240. break;
  241. case TypedArrayElementType.Uint8C:
  242. rawBytes[0] = TypeConverter.ToUint8Clamp(value.DoubleValue);
  243. break;
  244. case TypedArrayElementType.Int16:
  245. #if !NETSTANDARD2_1
  246. rawBytes = BitConverter.GetBytes((short) intValue);
  247. #else
  248. BitConverter.TryWriteBytes(rawBytes, (short) intValue);
  249. #endif
  250. break;
  251. case TypedArrayElementType.Uint16:
  252. #if !NETSTANDARD2_1
  253. rawBytes = BitConverter.GetBytes((ushort) intValue);
  254. #else
  255. BitConverter.TryWriteBytes(rawBytes, (ushort) intValue);
  256. #endif
  257. break;
  258. case TypedArrayElementType.Int32:
  259. #if !NETSTANDARD2_1
  260. rawBytes = BitConverter.GetBytes((uint) intValue);
  261. #else
  262. BitConverter.TryWriteBytes(rawBytes, (uint) intValue);
  263. #endif
  264. break;
  265. case TypedArrayElementType.Uint32:
  266. #if !NETSTANDARD2_1
  267. rawBytes = BitConverter.GetBytes((uint) intValue);
  268. #else
  269. BitConverter.TryWriteBytes(rawBytes, (uint) intValue);
  270. #endif
  271. break;
  272. default:
  273. ExceptionHelper.ThrowArgumentOutOfRangeException();
  274. return null;
  275. }
  276. }
  277. var elementSize = type.GetElementSize();
  278. if (!isLittleEndian && elementSize > 1)
  279. {
  280. System.Array.Reverse(rawBytes, 0, elementSize);
  281. }
  282. return rawBytes;
  283. }
  284. internal void Resize(uint newByteLength)
  285. {
  286. if (_arrayBufferMaxByteLength is null)
  287. {
  288. ExceptionHelper.ThrowTypeError(_engine.Realm);
  289. }
  290. if (newByteLength > _arrayBufferMaxByteLength)
  291. {
  292. ExceptionHelper.ThrowRangeError(_engine.Realm);
  293. }
  294. var oldBlock = _arrayBufferData ?? System.Array.Empty<byte>();
  295. var newBlock = CreateByteDataBlock(_engine.Realm, newByteLength);
  296. var copyLength = System.Math.Min(newByteLength, ArrayBufferByteLength);
  297. System.Array.Copy(oldBlock, newBlock, copyLength);
  298. _arrayBufferData = newBlock;
  299. }
  300. internal void AssertNotDetached()
  301. {
  302. if (IsDetachedBuffer)
  303. {
  304. ExceptionHelper.ThrowTypeError(_engine.Realm, "ArrayBuffer has been detached");
  305. }
  306. }
  307. }