JsArrayBuffer.cs 14 KB

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