WebAssemblyMemory.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  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. #ifdef ENABLE_WASM
  7. #include "WasmLimits.h"
  8. using namespace Js;
  9. WebAssemblyMemory::WebAssemblyMemory(ArrayBufferBase* buffer, uint32 initial, uint32 maximum, DynamicType * type) :
  10. DynamicObject(type),
  11. m_buffer(buffer),
  12. m_initial(initial),
  13. m_maximum(maximum)
  14. {
  15. Assert(buffer->IsWebAssemblyArrayBuffer());
  16. Assert(m_buffer);
  17. Assert(m_buffer->GetByteLength() >= UInt32Math::Mul<WebAssembly::PageSize>(initial));
  18. }
  19. _Must_inspect_result_ bool WebAssemblyMemory::AreLimitsValid(uint32 initial, uint32 maximum)
  20. {
  21. return initial <= maximum && initial <= Wasm::Limits::GetMaxMemoryInitialPages() && maximum <= Wasm::Limits::GetMaxMemoryMaximumPages();
  22. }
  23. _Must_inspect_result_ bool WebAssemblyMemory::AreLimitsValid(uint32 initial, uint32 maximum, uint32 bufferLength)
  24. {
  25. if (!AreLimitsValid(initial, maximum))
  26. {
  27. return false;
  28. }
  29. // Do the mul after initial checks to avoid potential unneeded OOM exception
  30. const uint32 initBytes = UInt32Math::Mul<WebAssembly::PageSize>(initial);
  31. const uint32 maxBytes = UInt32Math::Mul<WebAssembly::PageSize>(maximum);
  32. return initBytes <= bufferLength && bufferLength <= maxBytes;
  33. }
  34. /* static */
  35. bool
  36. WebAssemblyMemory::Is(Var value)
  37. {
  38. return JavascriptOperators::GetTypeId(value) == TypeIds_WebAssemblyMemory;
  39. }
  40. /* static */
  41. WebAssemblyMemory *
  42. WebAssemblyMemory::FromVar(Var value)
  43. {
  44. AssertOrFailFast(WebAssemblyMemory::Is(value));
  45. return static_cast<WebAssemblyMemory*>(value);
  46. }
  47. /* static */
  48. WebAssemblyMemory *
  49. WebAssemblyMemory::UnsafeFromVar(Var value)
  50. {
  51. Assert(WebAssemblyMemory::Is(value));
  52. return static_cast<WebAssemblyMemory*>(value);
  53. }
  54. Var
  55. WebAssemblyMemory::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
  56. {
  57. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  58. ARGUMENTS(args, callInfo);
  59. ScriptContext* scriptContext = function->GetScriptContext();
  60. AssertMsg(args.HasArg(), "Should always have implicit 'this'");
  61. Var newTarget = args.GetNewTarget();
  62. JavascriptOperators::GetAndAssertIsConstructorSuperCall(args);
  63. if (!(callInfo.Flags & CallFlags_New) || (newTarget && JavascriptOperators::IsUndefinedObject(newTarget)))
  64. {
  65. JavascriptError::ThrowTypeError(scriptContext, JSERR_ClassConstructorCannotBeCalledWithoutNew, _u("WebAssembly.Memory"));
  66. }
  67. if (args.Info.Count < 2 || !JavascriptOperators::IsObject(args[1]))
  68. {
  69. JavascriptError::ThrowTypeError(scriptContext, JSERR_NeedObject, _u("memoryDescriptor"));
  70. }
  71. DynamicObject * memoryDescriptor = JavascriptObject::FromVar(args[1]);
  72. Var initVar = JavascriptOperators::OP_GetProperty(memoryDescriptor, PropertyIds::initial, scriptContext);
  73. uint32 initial = WebAssembly::ToNonWrappingUint32(initVar, scriptContext);
  74. uint32 maximum = Wasm::Limits::GetMaxMemoryMaximumPages();
  75. bool hasMaximum = false;
  76. if (JavascriptOperators::OP_HasProperty(memoryDescriptor, PropertyIds::maximum, scriptContext))
  77. {
  78. hasMaximum = true;
  79. Var maxVar = JavascriptOperators::OP_GetProperty(memoryDescriptor, PropertyIds::maximum, scriptContext);
  80. maximum = WebAssembly::ToNonWrappingUint32(maxVar, scriptContext);
  81. }
  82. bool isShared = false;
  83. if (Wasm::Threads::IsEnabled() && JavascriptOperators::OP_HasProperty(memoryDescriptor, PropertyIds::shared, scriptContext))
  84. {
  85. if (!hasMaximum)
  86. {
  87. JavascriptError::ThrowTypeError(scriptContext, WASMERR_SharedNoMaximum);
  88. }
  89. Var sharedVar = JavascriptOperators::OP_GetProperty(memoryDescriptor, PropertyIds::shared, scriptContext);
  90. isShared = JavascriptConversion::ToBool(sharedVar, scriptContext);
  91. }
  92. return CreateMemoryObject(initial, maximum, isShared, scriptContext);
  93. }
  94. Var
  95. WebAssemblyMemory::EntryGrow(RecyclableObject* function, CallInfo callInfo, ...)
  96. {
  97. ScriptContext* scriptContext = function->GetScriptContext();
  98. PROBE_STACK(scriptContext, Js::Constants::MinStackDefault);
  99. ARGUMENTS(args, callInfo);
  100. AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'");
  101. Assert(!(callInfo.Flags & CallFlags_New));
  102. if (!WebAssemblyMemory::Is(args[0]))
  103. {
  104. JavascriptError::ThrowTypeError(scriptContext, WASMERR_NeedMemoryObject);
  105. }
  106. WebAssemblyMemory* memory = WebAssemblyMemory::FromVar(args[0]);
  107. Assert(ArrayBufferBase::Is(memory->m_buffer));
  108. if (memory->m_buffer->IsDetached())
  109. {
  110. JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray);
  111. }
  112. Var deltaVar = scriptContext->GetLibrary()->GetUndefined();
  113. if (args.Info.Count >= 2)
  114. {
  115. deltaVar = args[1];
  116. }
  117. uint32 deltaPages = WebAssembly::ToNonWrappingUint32(deltaVar, scriptContext);
  118. int32 oldPageCount = memory->GrowInternal(deltaPages);
  119. if (oldPageCount == -1)
  120. {
  121. JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange);
  122. }
  123. return JavascriptNumber::ToVar(oldPageCount, scriptContext);
  124. }
  125. int32
  126. WebAssemblyMemory::GrowInternal(uint32 deltaPages)
  127. {
  128. const uint64 deltaBytes = (uint64)deltaPages * WebAssembly::PageSize;
  129. if (deltaBytes > ArrayBuffer::MaxArrayBufferLength)
  130. {
  131. return -1;
  132. }
  133. const uint32 oldBytes = m_buffer->GetByteLength();
  134. const uint64 newBytesLong = deltaBytes + oldBytes;
  135. if (newBytesLong > ArrayBuffer::MaxArrayBufferLength)
  136. {
  137. return -1;
  138. }
  139. CompileAssert(ArrayBuffer::MaxArrayBufferLength <= UINT32_MAX);
  140. const uint32 newBytes = (uint32)newBytesLong;
  141. const uint32 oldPageCount = oldBytes / WebAssembly::PageSize;
  142. Assert(oldBytes % WebAssembly::PageSize == 0);
  143. const uint32 newPageCount = oldPageCount + deltaPages;
  144. if (newPageCount > m_maximum)
  145. {
  146. return -1;
  147. }
  148. AssertOrFailFast(m_buffer->IsWebAssemblyArrayBuffer());
  149. #ifdef ENABLE_WASM_THREADS
  150. if (m_buffer->IsSharedArrayBuffer())
  151. {
  152. AssertOrFailFast(Wasm::Threads::IsEnabled());
  153. if (!((WebAssemblySharedArrayBuffer*)GetBuffer())->GrowMemory(newBytes))
  154. {
  155. return -1;
  156. }
  157. }
  158. else
  159. #endif
  160. {
  161. JavascriptExceptionObject* caughtExceptionObject = nullptr;
  162. try
  163. {
  164. WebAssemblyArrayBuffer* newBuffer = ((WebAssemblyArrayBuffer*)GetBuffer())->GrowMemory(newBytes);
  165. if (newBuffer == nullptr)
  166. {
  167. return -1;
  168. }
  169. m_buffer = newBuffer;
  170. }
  171. catch (const JavascriptException& err)
  172. {
  173. caughtExceptionObject = err.GetAndClear();
  174. Assert(caughtExceptionObject && caughtExceptionObject == ThreadContext::GetContextForCurrentThread()->GetPendingOOMErrorObject());
  175. return -1;
  176. }
  177. }
  178. CompileAssert(ArrayBuffer::MaxArrayBufferLength / WebAssembly::PageSize <= INT32_MAX);
  179. return (int32)oldPageCount;
  180. }
  181. int32
  182. WebAssemblyMemory::GrowHelper(WebAssemblyMemory * mem, uint32 deltaPages)
  183. {
  184. JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Op_GrowWasmMemory);
  185. return mem->GrowInternal(deltaPages);
  186. JIT_HELPER_END(Op_GrowWasmMemory);
  187. }
  188. #if DBG
  189. void WebAssemblyMemory::TraceMemWrite(WebAssemblyMemory* mem, uint32 index, uint32 offset, Js::ArrayBufferView::ViewType viewType, uint32 bytecodeOffset, ScriptContext* context)
  190. {
  191. JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(Op_WasmMemoryTraceWrite);
  192. // Must call after the write
  193. Assert(mem);
  194. Output::Print(_u("#%04x "), bytecodeOffset);
  195. uint64 bigIndex = (uint64)index + (uint64)offset;
  196. if (index >= mem->m_buffer->GetByteLength())
  197. {
  198. Output::Print(_u("WasmMemoryTrace:: Writing out of bounds. %llu >= %u\n"), bigIndex, mem->m_buffer->GetByteLength());
  199. }
  200. if (offset)
  201. {
  202. Output::Print(_u("WasmMemoryTrace:: buf[%u + %u (%llu)]"), index, offset, bigIndex);
  203. }
  204. else
  205. {
  206. Output::Print(_u("WasmMemoryTrace:: buf[%u]"), index);
  207. }
  208. BYTE* buffer = mem->m_buffer->GetBuffer();
  209. switch (viewType)
  210. {
  211. case ArrayBufferView::ViewType::TYPE_INT8_TO_INT64:
  212. case ArrayBufferView::ViewType::TYPE_INT8: Output::Print(_u(".int8 = %d\n"), *(int8*)(buffer + bigIndex)); break;
  213. case ArrayBufferView::ViewType::TYPE_UINT8_TO_INT64:
  214. case ArrayBufferView::ViewType::TYPE_UINT8: Output::Print(_u(".uint8 = %u\n"), *(uint8*)(buffer + bigIndex)); break;
  215. case ArrayBufferView::ViewType::TYPE_INT16_TO_INT64:
  216. case ArrayBufferView::ViewType::TYPE_INT16: Output::Print(_u(".int16 = %d\n"), *(int16*)(buffer + bigIndex)); break;
  217. case ArrayBufferView::ViewType::TYPE_UINT16_TO_INT64:
  218. case ArrayBufferView::ViewType::TYPE_UINT16: Output::Print(_u(".uint16 = %u\n"), *(uint16*)(buffer + bigIndex)); break;
  219. case ArrayBufferView::ViewType::TYPE_INT32_TO_INT64:
  220. case ArrayBufferView::ViewType::TYPE_INT32: Output::Print(_u(".int32 = %d\n"), *(int32*)(buffer + bigIndex)); break;
  221. case ArrayBufferView::ViewType::TYPE_UINT32_TO_INT64:
  222. case ArrayBufferView::ViewType::TYPE_UINT32: Output::Print(_u(".uint32 = %u\n"), *(uint32*)(buffer + bigIndex)); break;
  223. case ArrayBufferView::ViewType::TYPE_FLOAT32: Output::Print(_u(".f32 = %.4f\n"), *(float*)(buffer + bigIndex)); break;
  224. case ArrayBufferView::ViewType::TYPE_FLOAT64: Output::Print(_u(".f64 = %.8f\n"), *(double*)(buffer + bigIndex)); break;
  225. case ArrayBufferView::ViewType::TYPE_INT64: Output::Print(_u(".int64 = %lld\n"), *(int64*)(buffer + bigIndex)); break;
  226. default:
  227. CompileAssert(ArrayBufferView::ViewType::TYPE_COUNT == 15);
  228. Assert(UNREACHED);
  229. }
  230. return;
  231. JIT_HELPER_END(Op_WasmMemoryTraceWrite);
  232. }
  233. #endif
  234. Var
  235. WebAssemblyMemory::EntryGetterBuffer(RecyclableObject* function, CallInfo callInfo, ...)
  236. {
  237. PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault);
  238. ARGUMENTS(args, callInfo);
  239. ScriptContext* scriptContext = function->GetScriptContext();
  240. Assert(!(callInfo.Flags & CallFlags_New));
  241. if (args.Info.Count == 0 || !WebAssemblyMemory::Is(args[0]))
  242. {
  243. JavascriptError::ThrowTypeError(scriptContext, WASMERR_NeedMemoryObject);
  244. }
  245. WebAssemblyMemory* memory = WebAssemblyMemory::FromVar(args[0]);
  246. Assert(ArrayBufferBase::Is(memory->m_buffer));
  247. return CrossSite::MarshalVar(scriptContext, memory->m_buffer);
  248. }
  249. WebAssemblyMemory *
  250. WebAssemblyMemory::CreateMemoryObject(uint32 initial, uint32 maximum, bool isShared, ScriptContext * scriptContext)
  251. {
  252. if (!AreLimitsValid(initial, maximum))
  253. {
  254. JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange);
  255. }
  256. uint32 byteLength = UInt32Math::Mul<WebAssembly::PageSize>(initial);
  257. ArrayBufferBase* buffer = nullptr;
  258. #ifdef ENABLE_WASM_THREADS
  259. if (isShared)
  260. {
  261. Assert(Wasm::Threads::IsEnabled());
  262. uint32 maxByteLength = UInt32Math::Mul<WebAssembly::PageSize>(maximum);
  263. buffer = scriptContext->GetLibrary()->CreateWebAssemblySharedArrayBuffer(byteLength, maxByteLength);
  264. }
  265. else
  266. #endif
  267. {
  268. buffer = scriptContext->GetLibrary()->CreateWebAssemblyArrayBuffer(byteLength);
  269. }
  270. Assert(buffer);
  271. if (byteLength > 0 && buffer->GetByteLength() == 0)
  272. {
  273. // Failed to allocate buffer
  274. return nullptr;
  275. }
  276. return RecyclerNewFinalized(scriptContext->GetRecycler(), WebAssemblyMemory, buffer, initial, maximum, scriptContext->GetLibrary()->GetWebAssemblyMemoryType());
  277. }
  278. WebAssemblyMemory * WebAssemblyMemory::CreateForExistingBuffer(uint32 initial, uint32 maximum, uint32 currentByteLength, ScriptContext * scriptContext)
  279. {
  280. if (!AreLimitsValid(initial, maximum, currentByteLength))
  281. {
  282. JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange);
  283. }
  284. ArrayBufferBase* buffer = scriptContext->GetLibrary()->CreateWebAssemblyArrayBuffer(currentByteLength);
  285. Assert(buffer);
  286. if (currentByteLength > 0 && buffer->GetByteLength() == 0)
  287. {
  288. // Failed to allocate buffer
  289. return nullptr;
  290. }
  291. return RecyclerNewFinalized(scriptContext->GetRecycler(), WebAssemblyMemory, buffer, initial, maximum, scriptContext->GetLibrary()->GetWebAssemblyMemoryType());
  292. }
  293. #ifdef ENABLE_WASM_THREADS
  294. WebAssemblyMemory * WebAssemblyMemory::CreateFromSharedContents(uint32 initial, uint32 maximum, SharedContents* sharedContents, ScriptContext * scriptContext)
  295. {
  296. if (!sharedContents || !AreLimitsValid(initial, maximum, sharedContents->bufferLength))
  297. {
  298. JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange);
  299. }
  300. ArrayBufferBase* buffer = scriptContext->GetLibrary()->CreateWebAssemblySharedArrayBuffer(sharedContents);
  301. return RecyclerNewFinalized(scriptContext->GetRecycler(), WebAssemblyMemory, buffer, initial, maximum, scriptContext->GetLibrary()->GetWebAssemblyMemoryType());
  302. }
  303. #endif
  304. ArrayBufferBase*
  305. WebAssemblyMemory::GetBuffer() const
  306. {
  307. return m_buffer;
  308. }
  309. uint
  310. WebAssemblyMemory::GetInitialLength() const
  311. {
  312. return m_initial;
  313. }
  314. uint
  315. WebAssemblyMemory::GetMaximumLength() const
  316. {
  317. return m_maximum;
  318. }
  319. uint
  320. WebAssemblyMemory::GetCurrentMemoryPages() const
  321. {
  322. return m_buffer->GetByteLength() / WebAssembly::PageSize;
  323. }
  324. #ifdef ENABLE_WASM_THREADS
  325. bool WebAssemblyMemory::IsSharedMemory() const
  326. {
  327. return WebAssemblySharedArrayBuffer::Is(m_buffer);
  328. }
  329. #endif
  330. #endif // ENABLE_WASM