2
0

EmitBuffer.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  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 "BackEnd.h"
  6. template class EmitBufferManager<FakeCriticalSection>;
  7. template class EmitBufferManager<CriticalSection>;
  8. //----------------------------------------------------------------------------
  9. // EmitBufferManager::EmitBufferManager
  10. // Constructor
  11. //----------------------------------------------------------------------------
  12. template <typename SyncObject>
  13. EmitBufferManager<SyncObject>::EmitBufferManager(AllocationPolicyManager * policyManager, ArenaAllocator * allocator,
  14. Js::ScriptContext * scriptContext, LPCWSTR name, bool allocXdata) :
  15. allocationHeap(policyManager, allocator, allocXdata),
  16. allocator(allocator),
  17. allocations(nullptr),
  18. scriptContext(scriptContext)
  19. {
  20. #if DBG_DUMP
  21. this->totalBytesCode = 0;
  22. this->totalBytesLoopBody = 0;
  23. this->totalBytesAlignment = 0;
  24. this->totalBytesCommitted = 0;
  25. this->totalBytesReserved = 0;
  26. this->name = name;
  27. #endif
  28. }
  29. //----------------------------------------------------------------------------
  30. // EmitBufferManager::~EmitBufferManager()
  31. // Free up all the VirtualAlloced memory
  32. //----------------------------------------------------------------------------
  33. template <typename SyncObject>
  34. EmitBufferManager<SyncObject>::~EmitBufferManager()
  35. {
  36. Clear();
  37. }
  38. template <typename SyncObject>
  39. void
  40. EmitBufferManager<SyncObject>::Decommit()
  41. {
  42. FreeAllocations(false);
  43. }
  44. template <typename SyncObject>
  45. void
  46. EmitBufferManager<SyncObject>::Clear()
  47. {
  48. FreeAllocations(true);
  49. }
  50. template <typename SyncObject>
  51. void
  52. EmitBufferManager<SyncObject>::FreeAllocations(bool release)
  53. {
  54. AutoRealOrFakeCriticalSection<SyncObject> autoCs(&this->criticalSection);
  55. #if DBG_DUMP
  56. if (!release && PHASE_STATS1(Js::EmitterPhase))
  57. {
  58. this->DumpAndResetStats(Js::Configuration::Global.flags.Filename);
  59. }
  60. #endif
  61. EmitBufferAllocation * allocation = this->allocations;
  62. while (allocation != nullptr)
  63. {
  64. BOOL isFreed;
  65. // In case of ThunkEmitter the script context would be null and we don't want to track that as code size.
  66. if (!release && (scriptContext != nullptr) && allocation->recorded)
  67. {
  68. this->scriptContext->GetThreadContext()->SubCodeSize(allocation->bytesCommitted);
  69. allocation->recorded = false;
  70. }
  71. #ifdef ENABLE_DEBUG_CONFIG_OPTIONS
  72. if(CONFIG_FLAG(CheckEmitBufferPermissions))
  73. {
  74. CheckBufferPermissions(allocation);
  75. }
  76. #endif
  77. if (release)
  78. {
  79. isFreed = this->allocationHeap.Free(allocation->allocation);
  80. }
  81. else
  82. {
  83. isFreed = this->allocationHeap.Decommit(allocation->allocation);
  84. }
  85. Assert(isFreed);
  86. allocation = allocation->nextAllocation;
  87. }
  88. if (release)
  89. {
  90. this->allocations = nullptr;
  91. }
  92. }
  93. class AutoCustomHeapPointer
  94. {
  95. public:
  96. AutoCustomHeapPointer(CustomHeap::Heap* allocationHeap, CustomHeap::Allocation* heapAllocation) :
  97. _allocationHeap(allocationHeap),
  98. _heapAllocation(heapAllocation)
  99. {
  100. }
  101. ~AutoCustomHeapPointer()
  102. {
  103. if (_heapAllocation)
  104. {
  105. _allocationHeap->Free(_heapAllocation);
  106. }
  107. }
  108. CustomHeap::Allocation* Detach()
  109. {
  110. CustomHeap::Allocation* allocation = _heapAllocation;
  111. Assert(allocation != nullptr);
  112. _heapAllocation = nullptr;
  113. return allocation;
  114. }
  115. private:
  116. CustomHeap::Allocation* _heapAllocation;
  117. CustomHeap::Heap* _allocationHeap;
  118. };
  119. //----------------------------------------------------------------------------
  120. // EmitBufferManager::NewAllocation
  121. // Create a new allocation
  122. //----------------------------------------------------------------------------
  123. template <typename SyncObject>
  124. EmitBufferAllocation *
  125. EmitBufferManager<SyncObject>::NewAllocation(size_t bytes, ushort pdataCount, ushort xdataSize, bool canAllocInPreReservedHeapPageSegment, bool isAnyJittedCode)
  126. {
  127. FAULTINJECT_MEMORY_THROW(L"JIT", bytes);
  128. Assert(this->criticalSection.IsLocked());
  129. PreReservedVirtualAllocWrapper * preReservedVirtualAllocator = nullptr;
  130. if (canAllocInPreReservedHeapPageSegment)
  131. {
  132. Assert(scriptContext && scriptContext->GetThreadContext());
  133. preReservedVirtualAllocator = this->scriptContext->GetThreadContext()->GetPreReservedVirtualAllocator();
  134. this->EnsurePreReservedPageAllocation(preReservedVirtualAllocator);
  135. }
  136. bool isAllJITCodeInPreReservedRegion = true;
  137. CustomHeap::Allocation* heapAllocation = this->allocationHeap.Alloc(bytes, pdataCount, xdataSize, canAllocInPreReservedHeapPageSegment, isAnyJittedCode, &isAllJITCodeInPreReservedRegion);
  138. if (!isAllJITCodeInPreReservedRegion)
  139. {
  140. this->scriptContext->GetThreadContext()->ResetIsAllJITCodeInPreReservedRegion();
  141. }
  142. if (heapAllocation == nullptr)
  143. {
  144. // This is used in interpreter scenario, thus we need to try to recover memory, if possible.
  145. // Can't simply throw as in JIT scenario, for which throw is what we want in order to give more mem to interpreter.
  146. JsUtil::ExternalApi::RecoverUnusedMemory();
  147. heapAllocation = this->allocationHeap.Alloc(bytes, pdataCount, xdataSize, canAllocInPreReservedHeapPageSegment, isAnyJittedCode, &isAllJITCodeInPreReservedRegion);
  148. }
  149. if (heapAllocation == nullptr)
  150. {
  151. Js::Throw::OutOfMemory();
  152. }
  153. AutoCustomHeapPointer allocatedMemory(&this->allocationHeap, heapAllocation);
  154. VerboseHeapTrace(L"New allocation: 0x%p, size: %p\n", heapAllocation->address, heapAllocation->size);
  155. EmitBufferAllocation * allocation = AnewStruct(this->allocator, EmitBufferAllocation);
  156. allocation->bytesCommitted = heapAllocation->size;
  157. allocation->allocation = allocatedMemory.Detach();
  158. allocation->bytesUsed = 0;
  159. allocation->nextAllocation = this->allocations;
  160. allocation->recorded = false;
  161. this->allocations = allocation;
  162. #if DBG
  163. heapAllocation->isAllocationUsed = true;
  164. #endif
  165. #if DBG_DUMP
  166. this->totalBytesCommitted += heapAllocation->size;
  167. #endif
  168. return allocation;
  169. }
  170. template <typename SyncObject>
  171. bool
  172. EmitBufferManager<SyncObject>::FreeAllocation(void* address)
  173. {
  174. AutoRealOrFakeCriticalSection<SyncObject> autoCs(&this->criticalSection);
  175. EmitBufferAllocation* previous = nullptr;
  176. EmitBufferAllocation* allocation = allocations;
  177. while(allocation != nullptr)
  178. {
  179. if (address >= allocation->allocation->address && address < (allocation->allocation->address + allocation->bytesUsed))
  180. {
  181. if (previous == nullptr)
  182. {
  183. this->allocations = allocation->nextAllocation;
  184. }
  185. else
  186. {
  187. previous->nextAllocation = allocation->nextAllocation;
  188. }
  189. if ((scriptContext != nullptr) && allocation->recorded)
  190. {
  191. this->scriptContext->GetThreadContext()->SubCodeSize(allocation->bytesCommitted);
  192. }
  193. VerboseHeapTrace(L"Freeing 0x%p, allocation: 0x%p\n", address, allocation->allocation->address);
  194. this->allocationHeap.Free(allocation->allocation);
  195. this->allocator->Free(allocation, sizeof(EmitBufferAllocation));
  196. return true;
  197. }
  198. previous = allocation;
  199. allocation = allocation->nextAllocation;
  200. }
  201. return false;
  202. }
  203. //----------------------------------------------------------------------------
  204. // EmitBufferManager::FinalizeAllocation
  205. // Fill the rest of the page with debugger breakpoint.
  206. //----------------------------------------------------------------------------
  207. template <typename SyncObject>
  208. bool EmitBufferManager<SyncObject>::FinalizeAllocation(EmitBufferAllocation *allocation)
  209. {
  210. Assert(this->criticalSection.IsLocked());
  211. DWORD bytes = allocation->BytesFree();
  212. if(bytes > 0)
  213. {
  214. BYTE* buffer = nullptr;
  215. this->GetBuffer(allocation, bytes, &buffer);
  216. if (!this->CommitBuffer(allocation, buffer, 0, /*sourceBuffer=*/ nullptr, /*alignPad=*/ bytes))
  217. {
  218. return false;
  219. }
  220. #if DBG_DUMP
  221. this->totalBytesCode -= bytes;
  222. #endif
  223. }
  224. return true;
  225. }
  226. template <typename SyncObject>
  227. EmitBufferAllocation* EmitBufferManager<SyncObject>::GetBuffer(EmitBufferAllocation *allocation, __in size_t bytes, __deref_bcount(bytes) BYTE** ppBuffer)
  228. {
  229. Assert(this->criticalSection.IsLocked());
  230. Assert(allocation->BytesFree() >= bytes);
  231. // In case of ThunkEmitter the script context would be null and we don't want to track that as code size.
  232. if (scriptContext && !allocation->recorded)
  233. {
  234. this->scriptContext->GetThreadContext()->AddCodeSize(allocation->bytesCommitted);
  235. allocation->recorded = true;
  236. }
  237. // The codegen buffer is beyond the alignment section - hence, we pass this pointer.
  238. *ppBuffer = allocation->GetUnused();
  239. return allocation;
  240. }
  241. //----------------------------------------------------------------------------
  242. // EmitBufferManager::Allocate
  243. // Allocates an executable buffer with a certain alignment
  244. // NOTE: This buffer is not readable or writable. Use CommitBuffer
  245. // to modify this buffer one page at a time.
  246. //----------------------------------------------------------------------------
  247. template <typename SyncObject>
  248. EmitBufferAllocation* EmitBufferManager<SyncObject>::AllocateBuffer(__in size_t bytes, __deref_bcount(bytes) BYTE** ppBuffer, ushort pdataCount /*=0*/, ushort xdataSize /*=0*/, bool canAllocInPreReservedHeapPageSegment /*=false*/,
  249. bool isAnyJittedCode /* = false*/)
  250. {
  251. AutoRealOrFakeCriticalSection<SyncObject> autoCs(&this->criticalSection);
  252. Assert(ppBuffer != nullptr);
  253. EmitBufferAllocation * allocation = this->NewAllocation(bytes, pdataCount, xdataSize, canAllocInPreReservedHeapPageSegment, isAnyJittedCode);
  254. GetBuffer(allocation, bytes, ppBuffer);
  255. #if DBG
  256. MEMORY_BASIC_INFORMATION memBasicInfo;
  257. size_t resultBytes = VirtualQuery(allocation->allocation->address, &memBasicInfo, sizeof(memBasicInfo));
  258. Assert(resultBytes != 0 && memBasicInfo.Protect == PAGE_EXECUTE);
  259. #endif
  260. return allocation;
  261. }
  262. #ifdef ENABLE_DEBUG_CONFIG_OPTIONS
  263. template <typename SyncObject>
  264. bool EmitBufferManager<SyncObject>::CheckCommitFaultInjection()
  265. {
  266. if (Js::Configuration::Global.flags.ForceOOMOnEBCommit == 0)
  267. {
  268. return false;
  269. }
  270. commitCount++;
  271. if (Js::Configuration::Global.flags.ForceOOMOnEBCommit == -1)
  272. {
  273. Output::Print(L"Commit count: %d\n", commitCount);
  274. }
  275. else if (commitCount == Js::Configuration::Global.flags.ForceOOMOnEBCommit)
  276. {
  277. return true;
  278. }
  279. return false;
  280. }
  281. #endif
  282. template <typename SyncObject>
  283. bool EmitBufferManager<SyncObject>::ProtectBufferWithExecuteReadWriteForInterpreter(EmitBufferAllocation* allocation)
  284. {
  285. Assert(this->criticalSection.IsLocked());
  286. Assert(allocation != nullptr);
  287. return (this->allocationHeap.ProtectAllocationWithExecuteReadWrite(allocation->allocation) == TRUE);
  288. }
  289. // Returns true if we successfully commit the buffer
  290. // Returns false if we OOM
  291. template <typename SyncObject>
  292. bool EmitBufferManager<SyncObject>::CommitReadWriteBufferForInterpreter(EmitBufferAllocation* allocation, _In_reads_bytes_(bufferSize) BYTE* pBuffer, _In_ size_t bufferSize)
  293. {
  294. Assert(this->criticalSection.IsLocked());
  295. Assert(allocation != nullptr);
  296. allocation->bytesUsed += bufferSize;
  297. #ifdef DEBUG
  298. this->totalBytesCode += bufferSize;
  299. #endif
  300. VerboseHeapTrace(L"Setting execute permissions on 0x%p, allocation: 0x%p\n", pBuffer, allocation->allocation->address);
  301. #ifdef ENABLE_DEBUG_CONFIG_OPTIONS
  302. if (CheckCommitFaultInjection())
  303. {
  304. return false;
  305. }
  306. #endif
  307. if (!this->allocationHeap.ProtectAllocationWithExecuteReadOnly(allocation->allocation))
  308. {
  309. return false;
  310. }
  311. FlushInstructionCache(AutoSystemInfo::Data.GetProcessHandle(), pBuffer, bufferSize);
  312. return true;
  313. }
  314. //----------------------------------------------------------------------------
  315. // EmitBufferManager::CommitBuffer
  316. // Aligns the buffer with DEBUG instructions.
  317. // Copies contents of source buffer to the destination buffer - at max of one page at a time.
  318. // This ensures that only 1 page is writable at any point of time.
  319. // Commit a buffer from the last AllocateBuffer call that is filled.
  320. //----------------------------------------------------------------------------
  321. template <typename SyncObject>
  322. bool
  323. EmitBufferManager<SyncObject>::CommitBuffer(EmitBufferAllocation* allocation, __out_bcount(bytes) BYTE* destBuffer, __in size_t bytes, __in_bcount(bytes) const BYTE* sourceBuffer, __in DWORD alignPad)
  324. {
  325. AutoRealOrFakeCriticalSection<SyncObject> autoCs(&this->criticalSection);
  326. Assert(destBuffer != nullptr);
  327. Assert(allocation != nullptr);
  328. BYTE *currentDestBuffer = allocation->GetUnused();
  329. BYTE *bufferToFlush = currentDestBuffer;
  330. Assert(allocation->BytesFree() >= bytes + alignPad);
  331. size_t bytesLeft = bytes + alignPad;
  332. size_t sizeToFlush = bytesLeft;
  333. // Copy the contents and set the alignment pad
  334. while(bytesLeft != 0)
  335. {
  336. DWORD spaceInCurrentPage = AutoSystemInfo::PageSize - ((size_t)currentDestBuffer & (AutoSystemInfo::PageSize - 1));
  337. size_t bytesToChange = bytesLeft > spaceInCurrentPage ? spaceInCurrentPage : bytesLeft;
  338. // Buffer and the bytes that are marked RWX - these will eventually be marked as 'EXCEUTE' only.
  339. BYTE* readWriteBuffer = currentDestBuffer;
  340. size_t readWriteBytes = bytesToChange;
  341. #ifdef ENABLE_DEBUG_CONFIG_OPTIONS
  342. if (CheckCommitFaultInjection())
  343. {
  344. return false;
  345. }
  346. #endif
  347. if (!this->allocationHeap.ProtectAllocationWithExecuteReadWrite(allocation->allocation, (char*)readWriteBuffer))
  348. {
  349. return false;
  350. }
  351. if (alignPad != 0)
  352. {
  353. DWORD alignBytes = alignPad < spaceInCurrentPage ? alignPad : spaceInCurrentPage;
  354. CustomHeap::FillDebugBreak(currentDestBuffer, alignBytes);
  355. alignPad -= alignBytes;
  356. currentDestBuffer += alignBytes;
  357. allocation->bytesUsed += alignBytes;
  358. bytesLeft -= alignBytes;
  359. bytesToChange -= alignBytes;
  360. #if DBG_DUMP
  361. this->totalBytesAlignment += alignBytes;
  362. #endif
  363. }
  364. // If there are bytes still left to be copied then we should do the copy.
  365. if(bytesToChange > 0)
  366. {
  367. AssertMsg(alignPad == 0, "If we are copying right now - we should be done with setting alignment.");
  368. memcpy_s(currentDestBuffer, allocation->BytesFree(), sourceBuffer, bytesToChange);
  369. currentDestBuffer += bytesToChange;
  370. sourceBuffer += bytesToChange;
  371. allocation->bytesUsed += bytesToChange;
  372. bytesLeft -= bytesToChange;
  373. }
  374. Assert(readWriteBuffer + readWriteBytes == currentDestBuffer);
  375. if (!this->allocationHeap.ProtectAllocationWithExecuteReadOnly(allocation->allocation, (char*)readWriteBuffer))
  376. {
  377. return false;
  378. }
  379. }
  380. FlushInstructionCache(AutoSystemInfo::Data.GetProcessHandle(), bufferToFlush, sizeToFlush);
  381. #if DBG_DUMP
  382. this->totalBytesCode += bytes;
  383. #endif
  384. //Finish the current EmitBufferAllocation
  385. return FinalizeAllocation(allocation);
  386. }
  387. template <typename SyncObject>
  388. void
  389. EmitBufferManager<SyncObject>::CompletePreviousAllocation(EmitBufferAllocation* allocation)
  390. {
  391. AutoRealOrFakeCriticalSection<SyncObject> autoCs(&this->criticalSection);
  392. if (allocation != nullptr)
  393. {
  394. allocation->bytesUsed = allocation->bytesCommitted;
  395. }
  396. }
  397. #ifdef ENABLE_DEBUG_CONFIG_OPTIONS
  398. template <typename SyncObject>
  399. void
  400. EmitBufferManager<SyncObject>::CheckBufferPermissions(EmitBufferAllocation *allocation)
  401. {
  402. AutoRealOrFakeCriticalSection<SyncObject> autoCs(&this->criticalSection);
  403. if(allocation->bytesCommitted == 0)
  404. return;
  405. MEMORY_BASIC_INFORMATION memInfo;
  406. BYTE *buffer = (BYTE*) allocation->allocation->address;
  407. SIZE_T size = allocation->bytesCommitted;
  408. while(1)
  409. {
  410. SIZE_T result = VirtualQuery(buffer, &memInfo, sizeof(memInfo));
  411. if(result == 0)
  412. {
  413. // VirtualQuery failed. This is not an expected condition, but it would be benign for the purposes of this check. Seems
  414. // to occur occasionally on process shutdown.
  415. break;
  416. }
  417. else if(memInfo.Protect == PAGE_EXECUTE_READWRITE)
  418. {
  419. Output::Print(L"ERROR: Found PAGE_EXECUTE_READWRITE page!\n");
  420. #ifdef DEBUG
  421. AssertMsg(FALSE, "Page was marked PAGE_EXECUTE_READWRITE");
  422. #else
  423. Fatal();
  424. #endif
  425. }
  426. // Figure out if we need to continue the query. The returned size might be larger than the size we requested,
  427. // for instance if more pages were allocated directly afterward, with the same permissions.
  428. if(memInfo.RegionSize >= size)
  429. {
  430. break;
  431. }
  432. // recalculate size for next iteration
  433. buffer += memInfo.RegionSize;
  434. size -= memInfo.RegionSize;
  435. if(size <= 0)
  436. {
  437. AssertMsg(FALSE, "Last VirtualQuery left us with unmatched regions");
  438. break;
  439. }
  440. }
  441. }
  442. #endif
  443. #if DBG_DUMP
  444. template <typename SyncObject>
  445. void
  446. EmitBufferManager<SyncObject>::DumpAndResetStats(wchar_t const * filename)
  447. {
  448. if (this->totalBytesCommitted != 0)
  449. {
  450. size_t wasted = this->totalBytesCommitted - this->totalBytesCode - this->totalBytesAlignment;
  451. Output::Print(L"Stats for %s: %s \n", name, filename);
  452. Output::Print(L" Total code size : %10d (%6.2f%% of committed)\n", this->totalBytesCode,
  453. (float)this->totalBytesCode * 100 / this->totalBytesCommitted);
  454. Output::Print(L" Total LoopBody code : %10d\n", this->totalBytesLoopBody);
  455. Output::Print(L" Total alignment size : %10d (%6.2f%% of committed)\n", this->totalBytesAlignment,
  456. (float)this->totalBytesAlignment * 100 / this->totalBytesCommitted);
  457. Output::Print(L" Total wasted size : %10d (%6.2f%% of committed)\n", wasted,
  458. (float)wasted * 100 / this->totalBytesCommitted);
  459. Output::Print(L" Total committed size : %10d (%6.2f%% of reserved)\n", this->totalBytesCommitted,
  460. (float)this->totalBytesCommitted * 100 / this->totalBytesReserved);
  461. Output::Print(L" Total reserved size : %10d\n", this->totalBytesReserved);
  462. }
  463. this->totalBytesCode = 0;
  464. this->totalBytesLoopBody = 0;
  465. this->totalBytesAlignment = 0;
  466. this->totalBytesCommitted = 0;
  467. this->totalBytesReserved = 0;
  468. }
  469. #endif