ArrayBuffer.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "RuntimeLibraryPch.h"
  6. namespace Js
  7. {
  8. bool ArrayBufferBase::Is(Var value)
  9. {
  10. return ArrayBuffer::Is(value) || SharedArrayBuffer::Is(value);
  11. }
  12. ArrayBufferBase* ArrayBufferBase::FromVar(Var value)
  13. {
  14. Assert(ArrayBufferBase::Is(value));
  15. return static_cast<ArrayBuffer *> (value);
  16. }
  17. ArrayBuffer* ArrayBuffer::NewFromDetachedState(DetachedStateBase* state, JavascriptLibrary *library)
  18. {
  19. ArrayBufferDetachedStateBase* arrayBufferState = (ArrayBufferDetachedStateBase *)state;
  20. ArrayBuffer *toReturn = nullptr;
  21. switch (arrayBufferState->allocationType)
  22. {
  23. case ArrayBufferAllocationType::CoTask:
  24. toReturn = library->CreateProjectionArraybuffer(arrayBufferState->buffer, arrayBufferState->bufferLength);
  25. break;
  26. case ArrayBufferAllocationType::Heap:
  27. case ArrayBufferAllocationType::MemAlloc:
  28. toReturn = library->CreateArrayBuffer(arrayBufferState->buffer, arrayBufferState->bufferLength);
  29. break;
  30. default:
  31. AssertMsg(false, "Unknown allocationType of ArrayBufferDetachedStateBase ");
  32. }
  33. return toReturn;
  34. }
  35. void ArrayBuffer::ClearParentsLength(ArrayBufferParent* parent)
  36. {
  37. if (parent == nullptr)
  38. {
  39. return;
  40. }
  41. switch (JavascriptOperators::GetTypeId(parent))
  42. {
  43. case TypeIds_Int8Array:
  44. case TypeIds_Uint8Array:
  45. case TypeIds_Uint8ClampedArray:
  46. case TypeIds_Int16Array:
  47. case TypeIds_Uint16Array:
  48. case TypeIds_Int32Array:
  49. case TypeIds_Uint32Array:
  50. case TypeIds_Float32Array:
  51. case TypeIds_Float64Array:
  52. case TypeIds_Int64Array:
  53. case TypeIds_Uint64Array:
  54. case TypeIds_CharArray:
  55. case TypeIds_BoolArray:
  56. TypedArrayBase::FromVar(parent)->length = 0;
  57. break;
  58. case TypeIds_DataView:
  59. DataView::FromVar(parent)->length = 0;
  60. break;
  61. default:
  62. AssertMsg(false, "We need an explicit case for any parent of ArrayBuffer.");
  63. break;
  64. }
  65. }
  66. ArrayBufferDetachedStateBase* ArrayBuffer::DetachAndGetState()
  67. {
  68. Assert(!this->isDetached);
  69. AutoPtr<ArrayBufferDetachedStateBase> arrayBufferState(this->CreateDetachedState(this->buffer, this->bufferLength));
  70. this->buffer = nullptr;
  71. this->bufferLength = 0;
  72. this->isDetached = true;
  73. if (this->primaryParent != nullptr && this->primaryParent->Get() == nullptr)
  74. {
  75. this->primaryParent = nullptr;
  76. }
  77. if (this->primaryParent != nullptr)
  78. {
  79. this->ClearParentsLength(this->primaryParent->Get());
  80. }
  81. if (this->otherParents != nullptr)
  82. {
  83. this->otherParents->Map([&](int index, RecyclerWeakReference<ArrayBufferParent>* item)
  84. {
  85. this->ClearParentsLength(item->Get());
  86. });
  87. }
  88. return arrayBufferState.Detach();
  89. }
  90. void ArrayBuffer::AddParent(ArrayBufferParent* parent)
  91. {
  92. if (this->primaryParent == nullptr || this->primaryParent->Get() == nullptr)
  93. {
  94. this->primaryParent = this->GetRecycler()->CreateWeakReferenceHandle(parent);
  95. }
  96. else
  97. {
  98. if (this->otherParents == nullptr)
  99. {
  100. this->otherParents = JsUtil::List<RecyclerWeakReference<ArrayBufferParent>*>::New(this->GetRecycler());
  101. }
  102. this->otherParents->Add(this->GetRecycler()->CreateWeakReferenceHandle(parent));
  103. }
  104. }
  105. void ArrayBuffer::RemoveParent(ArrayBufferParent* parent)
  106. {
  107. if (this->primaryParent != nullptr && this->primaryParent->Get() == parent)
  108. {
  109. this->primaryParent = nullptr;
  110. }
  111. else
  112. {
  113. int foundIndex = -1;
  114. bool parentFound = this->otherParents != nullptr && this->otherParents->MapUntil([&](int index, RecyclerWeakReference<ArrayBufferParent>* item)
  115. {
  116. if (item->Get() == parent)
  117. {
  118. foundIndex = index;
  119. return true;
  120. }
  121. return false;
  122. });
  123. if (parentFound)
  124. {
  125. this->otherParents->RemoveAt(foundIndex);
  126. }
  127. else
  128. {
  129. AssertMsg(false, "We shouldn't be clearing a parent that hasn't been set.");
  130. }
  131. }
  132. }
  133. uint32 ArrayBuffer::ToIndex(Var value, int32 errorCode, ScriptContext *scriptContext, uint32 MaxAllowedLength, bool checkSameValueZero)
  134. {
  135. if (JavascriptOperators::IsUndefined(value))
  136. {
  137. return 0;
  138. }
  139. if (TaggedInt::Is(value))
  140. {
  141. int64 index = TaggedInt::ToInt64(value);
  142. if (index < 0 || index > (int64)MaxAllowedLength)
  143. {
  144. JavascriptError::ThrowRangeError(scriptContext, errorCode);
  145. }
  146. return (uint32)index;
  147. }
  148. // Slower path
  149. double d = JavascriptConversion::ToInteger(value, scriptContext);
  150. if (d < 0.0 || d > (double)MaxAllowedLength)
  151. {
  152. JavascriptError::ThrowRangeError(scriptContext, errorCode);
  153. }
  154. if (checkSameValueZero)
  155. {
  156. Var integerIndex = JavascriptNumber::ToVarNoCheck(d, scriptContext);
  157. Var index = JavascriptNumber::ToVar(JavascriptConversion::ToLength(integerIndex, scriptContext), scriptContext);
  158. if (!JavascriptConversion::SameValueZero(integerIndex, index))
  159. {
  160. JavascriptError::ThrowRangeError(scriptContext, errorCode);
  161. }
  162. }
  163. return (uint32)d;
  164. }
  165. Var ArrayBuffer::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
  166. {
  167. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  168. ARGUMENTS(args, callInfo);
  169. ScriptContext* scriptContext = function->GetScriptContext();
  170. AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
  171. Var newTarget = callInfo.Flags & CallFlags_NewTarget ? args.Values[args.Info.Count] : args[0];
  172. bool isCtorSuperCall = (callInfo.Flags & CallFlags_New) && newTarget != nullptr && !JavascriptOperators::IsUndefined(newTarget);
  173. Assert(isCtorSuperCall || !(callInfo.Flags & CallFlags_New) || args[0] == nullptr);
  174. if (!(callInfo.Flags & CallFlags_New) || (newTarget && JavascriptOperators::IsUndefinedObject(newTarget)))
  175. {
  176. JavascriptError::ThrowTypeError(scriptContext, JSERR_ClassConstructorCannotBeCalledWithoutNew, _u("ArrayBuffer"));
  177. }
  178. uint32 byteLength = 0;
  179. if (args.Info.Count > 1)
  180. {
  181. byteLength = ToIndex(args[1], JSERR_ArrayLengthConstructIncorrect, scriptContext, MaxArrayBufferLength);
  182. }
  183. RecyclableObject* newArr = scriptContext->GetLibrary()->CreateArrayBuffer(byteLength);
  184. #if ENABLE_DEBUG_CONFIG_OPTIONS
  185. if (Js::Configuration::Global.flags.IsEnabled(Js::autoProxyFlag))
  186. {
  187. newArr = Js::JavascriptProxy::AutoProxyWrapper(newArr);
  188. }
  189. #endif
  190. return isCtorSuperCall ?
  191. JavascriptOperators::OrdinaryCreateFromConstructor(RecyclableObject::FromVar(newTarget), newArr, nullptr, scriptContext) :
  192. newArr;
  193. }
  194. // ArrayBuffer.prototype.byteLength as described in ES6 draft #20 section 24.1.4.1
  195. Var ArrayBuffer::EntryGetterByteLength(RecyclableObject* function, CallInfo callInfo, ...)
  196. {
  197. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  198. ARGUMENTS(args, callInfo);
  199. ScriptContext* scriptContext = function->GetScriptContext();
  200. Assert(!(callInfo.Flags & CallFlags_New));
  201. if (args.Info.Count == 0 || !ArrayBuffer::Is(args[0]))
  202. {
  203. JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedArrayBufferObject);
  204. }
  205. ArrayBuffer* arrayBuffer = ArrayBuffer::FromVar(args[0]);
  206. if (arrayBuffer->IsDetached())
  207. {
  208. return JavascriptNumber::ToVar(0, scriptContext);
  209. }
  210. return JavascriptNumber::ToVar(arrayBuffer->GetByteLength(), scriptContext);
  211. }
  212. // ArrayBuffer.isView as described in ES6 draft #20 section 24.1.3.1
  213. Var ArrayBuffer::EntryIsView(RecyclableObject* function, CallInfo callInfo, ...)
  214. {
  215. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  216. ARGUMENTS(args, callInfo);
  217. Assert(!(callInfo.Flags & CallFlags_New));
  218. AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
  219. JavascriptLibrary* library = function->GetScriptContext()->GetLibrary();
  220. Var arg = library->GetUndefined();
  221. if (args.Info.Count > 1)
  222. {
  223. arg = args[1];
  224. }
  225. // Only DataView or any TypedArray objects have [[ViewedArrayBuffer]] internal slots
  226. if (DataView::Is(arg) || TypedArrayBase::Is(arg))
  227. {
  228. return library->GetTrue();
  229. }
  230. return library->GetFalse();
  231. }
  232. // ArrayBuffer.transfer as described in Luke Wagner's proposal: https://gist.github.com/lukewagner/2735af7eea411e18cf20
  233. Var ArrayBuffer::EntryTransfer(RecyclableObject* function, CallInfo callInfo, ...)
  234. {
  235. ScriptContext* scriptContext = function->GetScriptContext();
  236. PROBE_STACK(scriptContext, Js::Constants::MinStackDefault);
  237. ARGUMENTS(args, callInfo);
  238. Assert(!(callInfo.Flags & CallFlags_New));
  239. CHAKRATEL_LANGSTATS_INC_BUILTINCOUNT(ArrayBuffer_Constructor_transfer);
  240. if (args.Info.Count < 2 || !ArrayBuffer::Is(args[1]))
  241. {
  242. JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedArrayBufferObject);
  243. }
  244. ArrayBuffer* arrayBuffer = ArrayBuffer::FromVar(args[1]);
  245. if (arrayBuffer->IsDetached())
  246. {
  247. JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("ArrayBuffer.transfer"));
  248. }
  249. uint32 newBufferLength = arrayBuffer->bufferLength;
  250. if (args.Info.Count >= 3)
  251. {
  252. newBufferLength = ToIndex(args[2], JSERR_ArrayLengthConstructIncorrect, scriptContext, MaxArrayBufferLength);
  253. }
  254. return arrayBuffer->TransferInternal(newBufferLength);
  255. }
  256. // ArrayBuffer.prototype.slice as described in ES6 draft #19 section 24.1.4.3.
  257. Var ArrayBuffer::EntrySlice(RecyclableObject* function, CallInfo callInfo, ...)
  258. {
  259. ScriptContext* scriptContext = function->GetScriptContext();
  260. PROBE_STACK(scriptContext, Js::Constants::MinStackDefault);
  261. ARGUMENTS(args, callInfo);
  262. AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
  263. Assert(!(callInfo.Flags & CallFlags_New));
  264. if (!ArrayBuffer::Is(args[0]))
  265. {
  266. JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedArrayBufferObject);
  267. }
  268. JavascriptLibrary* library = scriptContext->GetLibrary();
  269. ArrayBuffer* arrayBuffer = ArrayBuffer::FromVar(args[0]);
  270. if (arrayBuffer->IsDetached()) // 24.1.4.3: 5. If IsDetachedBuffer(O) is true, then throw a TypeError exception.
  271. {
  272. JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("ArrayBuffer.prototype.slice"));
  273. }
  274. int64 len = arrayBuffer->bufferLength;
  275. int64 start = 0, end = 0;
  276. int64 newLen;
  277. // If no start or end arguments, use the entire length
  278. if (args.Info.Count < 2)
  279. {
  280. newLen = len;
  281. }
  282. else
  283. {
  284. start = JavascriptArray::GetIndexFromVar(args[1], len, scriptContext);
  285. // If no end argument, use length as the end
  286. if (args.Info.Count < 3 || args[2] == library->GetUndefined())
  287. {
  288. end = len;
  289. }
  290. else
  291. {
  292. end = JavascriptArray::GetIndexFromVar(args[2], len, scriptContext);
  293. }
  294. newLen = end > start ? end - start : 0;
  295. }
  296. // We can't have allocated an ArrayBuffer with byteLength > MaxArrayBufferLength.
  297. // start and end are clamped to valid indices, so the new length also cannot exceed MaxArrayBufferLength.
  298. // Therefore, should be safe to cast down newLen to uint32.
  299. // TODO: If we ever support allocating ArrayBuffer with byteLength > MaxArrayBufferLength we may need to review this math.
  300. Assert(newLen < MaxArrayBufferLength);
  301. uint32 byteLength = static_cast<uint32>(newLen);
  302. ArrayBuffer* newBuffer = nullptr;
  303. if (scriptContext->GetConfig()->IsES6SpeciesEnabled())
  304. {
  305. Var constructorVar = JavascriptOperators::SpeciesConstructor(arrayBuffer, scriptContext->GetLibrary()->GetArrayBufferConstructor(), scriptContext);
  306. JavascriptFunction* constructor = JavascriptFunction::FromVar(constructorVar);
  307. Js::Var constructorArgs[] = { constructor, JavascriptNumber::ToVar(byteLength, scriptContext) };
  308. Js::CallInfo constructorCallInfo(Js::CallFlags_New, _countof(constructorArgs));
  309. Js::Var newVar = JavascriptOperators::NewScObject(constructor, Js::Arguments(constructorCallInfo, constructorArgs), scriptContext);
  310. if (!ArrayBuffer::Is(newVar)) // 24.1.4.3: 19.If new does not have an [[ArrayBufferData]] internal slot throw a TypeError exception.
  311. {
  312. JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedArrayBufferObject);
  313. }
  314. newBuffer = ArrayBuffer::FromVar(newVar);
  315. if (newBuffer->IsDetached()) // 24.1.4.3: 21. If IsDetachedBuffer(new) is true, then throw a TypeError exception.
  316. {
  317. JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("ArrayBuffer.prototype.slice"));
  318. }
  319. if (newBuffer == arrayBuffer) // 24.1.4.3: 22. If SameValue(new, O) is true, then throw a TypeError exception.
  320. {
  321. JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedArrayBufferObject);
  322. }
  323. if (newBuffer->bufferLength < byteLength) // 24.1.4.3: 23.If the value of new's [[ArrayBufferByteLength]] internal slot < newLen, then throw a TypeError exception.
  324. {
  325. JavascriptError::ThrowTypeError(scriptContext, JSERR_ArgumentOutOfRange, _u("ArrayBuffer.prototype.slice"));
  326. }
  327. }
  328. else
  329. {
  330. newBuffer = library->CreateArrayBuffer(byteLength);
  331. }
  332. Assert(newBuffer);
  333. Assert(newBuffer->bufferLength >= byteLength);
  334. if (arrayBuffer->IsDetached()) // 24.1.4.3: 24. NOTE: Side-effects of the above steps may have detached O. 25. If IsDetachedBuffer(O) is true, then throw a TypeError exception.
  335. {
  336. JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("ArrayBuffer.prototype.slice"));
  337. }
  338. // Don't bother doing memcpy if we aren't copying any elements
  339. if (byteLength > 0)
  340. {
  341. AssertMsg(arrayBuffer->buffer != nullptr, "buffer must not be null when we copy from it");
  342. js_memcpy_s(newBuffer->buffer, byteLength, arrayBuffer->buffer + start, byteLength);
  343. }
  344. return newBuffer;
  345. }
  346. Var ArrayBuffer::EntryGetterSymbolSpecies(RecyclableObject* function, CallInfo callInfo, ...)
  347. {
  348. ARGUMENTS(args, callInfo);
  349. Assert(args.Info.Count > 0);
  350. return args[0];
  351. }
  352. ArrayBuffer* ArrayBuffer::FromVar(Var aValue)
  353. {
  354. AssertMsg(Is(aValue), "var must be an ArrayBuffer");
  355. return static_cast<ArrayBuffer *>(RecyclableObject::FromVar(aValue));
  356. }
  357. bool ArrayBuffer::Is(Var aValue)
  358. {
  359. return JavascriptOperators::GetTypeId(aValue) == TypeIds_ArrayBuffer;
  360. }
  361. template <class Allocator>
  362. ArrayBuffer::ArrayBuffer(uint32 length, DynamicType * type, Allocator allocator) :
  363. ArrayBufferBase(type), mIsAsmJsBuffer(false), isBufferCleared(false),isDetached(false)
  364. {
  365. buffer = nullptr;
  366. bufferLength = 0;
  367. if (length > MaxArrayBufferLength)
  368. {
  369. JavascriptError::ThrowTypeError(GetScriptContext(), JSERR_FunctionArgument_Invalid);
  370. }
  371. else if (length > 0)
  372. {
  373. Recycler* recycler = GetType()->GetLibrary()->GetRecycler();
  374. if (recycler->ReportExternalMemoryAllocation(length))
  375. {
  376. buffer = (BYTE*)allocator(length);
  377. if (buffer == nullptr)
  378. {
  379. recycler->ReportExternalMemoryFree(length);
  380. }
  381. }
  382. if (buffer == nullptr)
  383. {
  384. recycler->CollectNow<CollectOnTypedArrayAllocation>();
  385. if (recycler->ReportExternalMemoryAllocation(length))
  386. {
  387. buffer = (BYTE*)allocator(length);
  388. if (buffer == nullptr)
  389. {
  390. recycler->ReportExternalMemoryFailure(length);
  391. }
  392. }
  393. else
  394. {
  395. JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
  396. }
  397. }
  398. if (buffer != nullptr)
  399. {
  400. bufferLength = length;
  401. ZeroMemory(buffer, bufferLength);
  402. }
  403. }
  404. }
  405. ArrayBuffer::ArrayBuffer(byte* buffer, uint32 length, DynamicType * type) :
  406. buffer(buffer), bufferLength(length), ArrayBufferBase(type), mIsAsmJsBuffer(false), isDetached(false)
  407. {
  408. if (length > MaxArrayBufferLength)
  409. {
  410. JavascriptError::ThrowTypeError(GetScriptContext(), JSERR_FunctionArgument_Invalid);
  411. }
  412. }
  413. BOOL ArrayBuffer::GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
  414. {
  415. stringBuilder->AppendCppLiteral(_u("Object, (ArrayBuffer)"));
  416. return TRUE;
  417. }
  418. BOOL ArrayBuffer::GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext)
  419. {
  420. stringBuilder->AppendCppLiteral(_u("[object ArrayBuffer]"));
  421. return TRUE;
  422. }
  423. #if ENABLE_TTD
  424. void ArrayBufferParent::MarkVisitKindSpecificPtrs(TTD::SnapshotExtractor* extractor)
  425. {
  426. extractor->MarkVisitVar(this->arrayBuffer);
  427. }
  428. void ArrayBufferParent::ProcessCorePaths()
  429. {
  430. this->GetScriptContext()->TTDWellKnownInfo->EnqueueNewPathVarAsNeeded(this, this->arrayBuffer, _u("!buffer"));
  431. }
  432. #endif
  433. JavascriptArrayBuffer::JavascriptArrayBuffer(uint32 length, DynamicType * type) :
  434. ArrayBuffer(length, type, (IsValidVirtualBufferLength(length)) ? AllocWrapper : malloc)
  435. {
  436. }
  437. JavascriptArrayBuffer::JavascriptArrayBuffer(byte* buffer, uint32 length, DynamicType * type) :
  438. ArrayBuffer(buffer, length, type)
  439. {
  440. }
  441. JavascriptArrayBuffer::JavascriptArrayBuffer(DynamicType * type): ArrayBuffer(0, type, malloc)
  442. {
  443. }
  444. JavascriptArrayBuffer* JavascriptArrayBuffer::Create(uint32 length, DynamicType * type)
  445. {
  446. Recycler* recycler = type->GetScriptContext()->GetRecycler();
  447. JavascriptArrayBuffer* result = RecyclerNewFinalized(recycler, JavascriptArrayBuffer, length, type);
  448. Assert(result);
  449. recycler->AddExternalMemoryUsage(length);
  450. return result;
  451. }
  452. JavascriptArrayBuffer* JavascriptArrayBuffer::Create(byte* buffer, uint32 length, DynamicType * type)
  453. {
  454. Recycler* recycler = type->GetScriptContext()->GetRecycler();
  455. JavascriptArrayBuffer* result = RecyclerNewFinalized(recycler, JavascriptArrayBuffer, buffer, length, type);
  456. Assert(result);
  457. recycler->AddExternalMemoryUsage(length);
  458. return result;
  459. }
  460. ArrayBufferDetachedStateBase* JavascriptArrayBuffer::CreateDetachedState(BYTE* buffer, uint32 bufferLength)
  461. {
  462. #if _WIN64
  463. if (IsValidVirtualBufferLength(bufferLength))
  464. {
  465. return HeapNew(ArrayBufferDetachedState<FreeFn>, buffer, bufferLength, FreeMemAlloc, ArrayBufferAllocationType::MemAlloc);
  466. }
  467. else
  468. {
  469. return HeapNew(ArrayBufferDetachedState<FreeFn>, buffer, bufferLength, free, ArrayBufferAllocationType::Heap);
  470. }
  471. #else
  472. return HeapNew(ArrayBufferDetachedState<FreeFn>, buffer, bufferLength, free, ArrayBufferAllocationType::Heap);
  473. #endif
  474. }
  475. bool JavascriptArrayBuffer::IsValidAsmJsBufferLength(uint length, bool forceCheck)
  476. {
  477. #if _WIN64
  478. /*
  479. 1. length >= 2^16
  480. 2. length is power of 2 or (length > 2^24 and length is multiple of 2^24)
  481. 3. length is a multiple of 4K
  482. */
  483. return ((length >= 0x10000) &&
  484. (((length & (~length + 1)) == length) ||
  485. (length >= 0x1000000 && ((length & 0xFFFFFF) == 0))
  486. ) &&
  487. ((length % AutoSystemInfo::PageSize) == 0)
  488. );
  489. #else
  490. if (forceCheck)
  491. {
  492. return ((length >= 0x10000) &&
  493. (((length & (~length + 1)) == length) ||
  494. (length >= 0x1000000 && ((length & 0xFFFFFF) == 0))
  495. ) &&
  496. ((length & 0x0FFF) == 0)
  497. );
  498. }
  499. return false;
  500. #endif
  501. }
  502. bool JavascriptArrayBuffer::IsValidVirtualBufferLength(uint length)
  503. {
  504. #if _WIN64
  505. /*
  506. 1. length >= 2^16
  507. 2. length is power of 2 or (length > 2^24 and length is multiple of 2^24)
  508. 3. length is a multiple of 4K
  509. */
  510. return (!PHASE_OFF1(Js::TypedArrayVirtualPhase) &&
  511. (length >= 0x10000) &&
  512. (((length & (~length + 1)) == length) ||
  513. (length >= 0x1000000 &&
  514. ((length & 0xFFFFFF) == 0)
  515. )
  516. ) &&
  517. ((length % AutoSystemInfo::PageSize) == 0)
  518. );
  519. #else
  520. return false;
  521. #endif
  522. }
  523. void JavascriptArrayBuffer::Finalize(bool isShutdown)
  524. {
  525. // In debugger scenario, ScriptAuthor can create scriptContext and delete scriptContext
  526. // explicitly. So for the builtin, while javascriptLibrary is still alive fine, the
  527. // matching scriptContext might have been deleted and the javascriptLibrary->scriptContext
  528. // field reset (but javascriptLibrary is still alive).
  529. // Use the recycler field off library instead of scriptcontext to avoid av.
  530. // Recycler may not be available at Dispose. We need to
  531. // free the memory and report that it has been freed at the same
  532. // time. Otherwise, AllocationPolicyManager is unable to provide correct feedback
  533. #if _WIN64
  534. //AsmJS Virtual Free
  535. //TOD - see if isBufferCleared need to be added for free too
  536. if (IsValidVirtualBufferLength(this->bufferLength) && !isBufferCleared)
  537. {
  538. LPVOID startBuffer = (LPVOID)((uint64)buffer);
  539. BOOL fSuccess = VirtualFree((LPVOID)startBuffer, 0, MEM_RELEASE);
  540. Assert(fSuccess);
  541. isBufferCleared = true;
  542. }
  543. else
  544. {
  545. free(buffer);
  546. }
  547. #else
  548. free(buffer);
  549. #endif
  550. Recycler* recycler = GetType()->GetLibrary()->GetRecycler();
  551. recycler->ReportExternalMemoryFree(bufferLength);
  552. buffer = nullptr;
  553. bufferLength = 0;
  554. }
  555. void JavascriptArrayBuffer::Dispose(bool isShutdown)
  556. {
  557. /* See JavascriptArrayBuffer::Finalize */
  558. }
  559. // Copy memory from src to dst, truncate if dst smaller, zero extra memory
  560. // if dst larger
  561. static void MemCpyZero(__bcount(dstSize) BYTE* dst, size_t dstSize,
  562. __in_bcount(count) const BYTE* src, size_t count)
  563. {
  564. js_memcpy_s(dst, dstSize, src, min(dstSize, count));
  565. if (dstSize > count)
  566. {
  567. ZeroMemory(dst + count, dstSize - count);
  568. }
  569. }
  570. // Same as realloc but zero newly allocated portion if newSize > oldSize
  571. static BYTE* ReallocZero(BYTE* ptr, size_t oldSize, size_t newSize)
  572. {
  573. BYTE* ptrNew = (BYTE*)realloc(ptr, newSize);
  574. if (ptrNew && newSize > oldSize)
  575. {
  576. ZeroMemory(ptrNew + oldSize, newSize - oldSize);
  577. }
  578. return ptrNew;
  579. }
  580. ArrayBuffer * JavascriptArrayBuffer::TransferInternal(uint32 newBufferLength)
  581. {
  582. ArrayBuffer* newArrayBuffer;
  583. Recycler* recycler = this->GetRecycler();
  584. // Report differential external memory allocation.
  585. // If current bufferLength == 0, new ArrayBuffer creation records the allocation
  586. // so no need to do it here.
  587. if (this->bufferLength > 0 && newBufferLength != this->bufferLength)
  588. {
  589. // Expanding buffer
  590. if (newBufferLength > this->bufferLength)
  591. {
  592. if (!recycler->ReportExternalMemoryAllocation(newBufferLength - this->bufferLength))
  593. {
  594. recycler->CollectNow<CollectOnTypedArrayAllocation>();
  595. if (!recycler->ReportExternalMemoryAllocation(newBufferLength - this->bufferLength))
  596. {
  597. JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
  598. }
  599. }
  600. }
  601. // Contracting buffer
  602. else
  603. {
  604. recycler->ReportExternalMemoryFree(this->bufferLength - newBufferLength);
  605. }
  606. }
  607. if (newBufferLength == 0 || this->bufferLength == 0)
  608. {
  609. newArrayBuffer = GetLibrary()->CreateArrayBuffer(newBufferLength);
  610. }
  611. else
  612. {
  613. BYTE * newBuffer = nullptr;
  614. if (IsValidVirtualBufferLength(this->bufferLength))
  615. {
  616. if (IsValidVirtualBufferLength(newBufferLength))
  617. {
  618. // we are transferring between an optimized buffer using a length that can be optimized
  619. if (newBufferLength < this->bufferLength)
  620. {
  621. #pragma prefast(suppress:6250, "Calling 'VirtualFree' without the MEM_RELEASE flag might free memory but not address descriptors (VADs).")
  622. VirtualFree(this->buffer + newBufferLength, this->bufferLength - newBufferLength, MEM_DECOMMIT);
  623. }
  624. else if (newBufferLength > this->bufferLength)
  625. {
  626. LPVOID newMem = VirtualAlloc(this->buffer + this->bufferLength, newBufferLength - this->bufferLength, MEM_COMMIT, PAGE_READWRITE);
  627. if (!newMem)
  628. {
  629. recycler->ReportExternalMemoryFailure(newBufferLength);
  630. JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
  631. }
  632. }
  633. newBuffer = this->buffer;
  634. }
  635. else
  636. {
  637. // we are transferring from an optimized buffer, but the new length isn't compatible, so start over and copy to new memory
  638. newBuffer = (BYTE*)malloc(newBufferLength);
  639. if (!newBuffer)
  640. {
  641. recycler->ReportExternalMemoryFailure(newBufferLength);
  642. JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
  643. }
  644. MemCpyZero(newBuffer, newBufferLength, this->buffer, this->bufferLength);
  645. }
  646. }
  647. else
  648. {
  649. if (IsValidVirtualBufferLength(newBufferLength))
  650. {
  651. // we are transferring from an unoptimized buffer, but new length can be optimized, so move to that
  652. newBuffer = (BYTE*)JavascriptArrayBuffer::AllocWrapper(newBufferLength);
  653. MemCpyZero(newBuffer, newBufferLength, this->buffer, this->bufferLength);
  654. }
  655. else if (newBufferLength != this->bufferLength)
  656. {
  657. // both sides will just be regular ArrayBuffer, so realloc
  658. newBuffer = ReallocZero(this->buffer, this->bufferLength, newBufferLength);
  659. if (!newBuffer)
  660. {
  661. recycler->ReportExternalMemoryFailure(newBufferLength);
  662. JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
  663. }
  664. }
  665. else
  666. {
  667. newBuffer = this->buffer;
  668. }
  669. }
  670. newArrayBuffer = GetLibrary()->CreateArrayBuffer(newBuffer, newBufferLength);
  671. }
  672. AutoDiscardPTR<Js::ArrayBufferDetachedStateBase> state(DetachAndGetState());
  673. state->MarkAsClaimed();
  674. return newArrayBuffer;
  675. }
  676. #if ENABLE_TTD
  677. TTD::NSSnapObjects::SnapObjectType JavascriptArrayBuffer::GetSnapTag_TTD() const
  678. {
  679. return TTD::NSSnapObjects::SnapObjectType::SnapArrayBufferObject;
  680. }
  681. void JavascriptArrayBuffer::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
  682. {
  683. TTD::NSSnapObjects::SnapArrayBufferInfo* sabi = alloc.SlabAllocateStruct<TTD::NSSnapObjects::SnapArrayBufferInfo>();
  684. sabi->Length = this->GetByteLength();
  685. if(sabi->Length == 0)
  686. {
  687. sabi->Buff = nullptr;
  688. }
  689. else
  690. {
  691. sabi->Buff = alloc.SlabAllocateArray<byte>(sabi->Length);
  692. memcpy(sabi->Buff, this->GetBuffer(), sabi->Length);
  693. }
  694. TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapArrayBufferInfo*, TTD::NSSnapObjects::SnapObjectType::SnapArrayBufferObject>(objData, sabi);
  695. }
  696. #endif
  697. ProjectionArrayBuffer::ProjectionArrayBuffer(uint32 length, DynamicType * type) :
  698. ArrayBuffer(length, type, CoTaskMemAlloc)
  699. {
  700. }
  701. ProjectionArrayBuffer::ProjectionArrayBuffer(byte* buffer, uint32 length, DynamicType * type) :
  702. ArrayBuffer(buffer, length, type)
  703. {
  704. }
  705. ProjectionArrayBuffer* ProjectionArrayBuffer::Create(uint32 length, DynamicType * type)
  706. {
  707. Recycler* recycler = type->GetScriptContext()->GetRecycler();
  708. recycler->AddExternalMemoryUsage(length);
  709. return RecyclerNewFinalized(recycler, ProjectionArrayBuffer, length, type);
  710. }
  711. ProjectionArrayBuffer* ProjectionArrayBuffer::Create(byte* buffer, uint32 length, DynamicType * type)
  712. {
  713. Recycler* recycler = type->GetScriptContext()->GetRecycler();
  714. // This is user passed [in] buffer, user should AddExternalMemoryUsage before calling jscript, but
  715. // I don't see we ask everyone to do this. Let's add the memory pressure here as well.
  716. recycler->AddExternalMemoryUsage(length);
  717. return RecyclerNewFinalized(recycler, ProjectionArrayBuffer, buffer, length, type);
  718. }
  719. void ProjectionArrayBuffer::Dispose(bool isShutdown)
  720. {
  721. CoTaskMemFree(buffer);
  722. }
  723. ArrayBuffer * ProjectionArrayBuffer::TransferInternal(uint32 newBufferLength)
  724. {
  725. ArrayBuffer* newArrayBuffer;
  726. if (newBufferLength == 0 || this->bufferLength == 0)
  727. {
  728. newArrayBuffer = GetLibrary()->CreateProjectionArraybuffer(newBufferLength);
  729. }
  730. else
  731. {
  732. BYTE * newBuffer = (BYTE*)CoTaskMemRealloc(this->buffer, newBufferLength);
  733. if (!newBuffer)
  734. {
  735. JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
  736. }
  737. newArrayBuffer = GetLibrary()->CreateProjectionArraybuffer(newBuffer, newBufferLength);
  738. }
  739. AutoDiscardPTR<Js::ArrayBufferDetachedStateBase> state(DetachAndGetState());
  740. state->MarkAsClaimed();
  741. return newArrayBuffer;
  742. }
  743. ExternalArrayBuffer::ExternalArrayBuffer(byte *buffer, uint32 length, DynamicType *type)
  744. : ArrayBuffer(buffer, length, type)
  745. {
  746. }
  747. #if ENABLE_TTD
  748. TTD::NSSnapObjects::SnapObjectType ExternalArrayBuffer::GetSnapTag_TTD() const
  749. {
  750. //We re-map ExternalArrayBuffers to regular buffers since the 'real' host will be gone when we replay
  751. return TTD::NSSnapObjects::SnapObjectType::SnapArrayBufferObject;
  752. }
  753. void ExternalArrayBuffer::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc)
  754. {
  755. TTD::NSSnapObjects::SnapArrayBufferInfo* sabi = alloc.SlabAllocateStruct<TTD::NSSnapObjects::SnapArrayBufferInfo>();
  756. sabi->Length = this->GetByteLength();
  757. if(sabi->Length == 0)
  758. {
  759. sabi->Buff = nullptr;
  760. }
  761. else
  762. {
  763. sabi->Buff = alloc.SlabAllocateArray<byte>(sabi->Length);
  764. memcpy(sabi->Buff, this->GetBuffer(), sabi->Length);
  765. }
  766. TTD::NSSnapObjects::StdExtractSetKindSpecificInfo<TTD::NSSnapObjects::SnapArrayBufferInfo*, TTD::NSSnapObjects::SnapObjectType::SnapArrayBufferObject>(objData, sabi);
  767. }
  768. #endif
  769. }