Skip to content

Commit f2b5bb2

Browse files
committed
[1.6>1.7] [MERGE #3468 @Cellule] Strengthen WebAssemblyArrayBuffer.GrowMemory
Merge pull request #3468 from Cellule:users/micfer/wasm/grow_memory Make reporting external memory allocation cleaner with a wrapper and ensure we don't end up in a bad state after ReAlloc
2 parents 992b1d7 + 8403578 commit f2b5bb2

File tree

6 files changed

+103
-58
lines changed

6 files changed

+103
-58
lines changed

lib/Common/Memory/Recycler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,7 @@ Recycler::AddExternalMemoryUsage(size_t size)
11041104
CollectNow<CollectOnAllocation>();
11051105
}
11061106

1107-
BOOL Recycler::ReportExternalMemoryAllocation(size_t size)
1107+
bool Recycler::RequestExternalMemoryAllocation(size_t size)
11081108
{
11091109
return recyclerPageAllocator.RequestAlloc(size);
11101110
}

lib/Common/Memory/Recycler.h

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1202,9 +1202,12 @@ class Recycler
12021202

12031203
template <CollectionFlags flags>
12041204
bool FinishDisposeObjectsNow();
1205-
BOOL ReportExternalMemoryAllocation(size_t size);
1205+
bool RequestExternalMemoryAllocation(size_t size);
12061206
void ReportExternalMemoryFailure(size_t size);
12071207
void ReportExternalMemoryFree(size_t size);
1208+
// ExternalAllocFunc returns true when allocation succeeds
1209+
template <typename ExternalAllocFunc>
1210+
bool DoExternalAllocation(size_t size, ExternalAllocFunc externalAllocFunc);
12081211

12091212
#ifdef TRACE_OBJECT_LIFETIME
12101213
#define DEFINE_RECYCLER_ALLOC_TRACE(AllocFunc, AllocWithAttributesFunc, attributes) \
@@ -2532,3 +2535,35 @@ extern bool IsLikelyRuntimeFalseReference(
25322535
#else
25332536
#define DECLARE_RECYCLER_VERIFY_MARK_FRIEND()
25342537
#endif
2538+
2539+
template <typename ExternalAllocFunc>
2540+
bool Recycler::DoExternalAllocation(size_t size, ExternalAllocFunc externalAllocFunc)
2541+
{
2542+
// Request external memory allocation
2543+
if (!RequestExternalMemoryAllocation(size))
2544+
{
2545+
// Attempt to free some memory then try again
2546+
CollectNow<CollectOnTypedArrayAllocation>();
2547+
if (!RequestExternalMemoryAllocation(size))
2548+
{
2549+
return false;
2550+
}
2551+
}
2552+
struct AutoExternalAllocation
2553+
{
2554+
bool allocationSucceeded = false;
2555+
Recycler* recycler;
2556+
size_t size;
2557+
AutoExternalAllocation(Recycler* recycler, size_t size): recycler(recycler), size(size) {}
2558+
// In case the externalAllocFunc throws or fails, the destructor will report the failure
2559+
~AutoExternalAllocation() { if (!allocationSucceeded) recycler->ReportExternalMemoryFailure(size); }
2560+
};
2561+
AutoExternalAllocation externalAllocation(this, size);
2562+
if (externalAllocFunc())
2563+
{
2564+
this->AddExternalMemoryUsage(size);
2565+
externalAllocation.allocationSucceeded = true;
2566+
return true;
2567+
}
2568+
return false;
2569+
}

lib/Runtime/Base/ThreadContext.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1802,7 +1802,7 @@ class AutoDisableInterrupt
18021802
AssertOrFailFast(false);
18031803
}
18041804
}
1805-
1805+
void RequireExplicitCompletion() { m_explicitCompletion = true; }
18061806
void Completed() { m_operationCompleted = true; }
18071807

18081808
private:

lib/Runtime/Library/ArrayBuffer.cpp

Lines changed: 62 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,10 @@ namespace Js
208208
}
209209
}
210210

211-
ArrayBufferDetachedStateBase* ArrayBuffer::DetachAndGetState()
211+
void ArrayBuffer::Detach()
212212
{
213213
Assert(!this->isDetached);
214214

215-
AutoPtr<ArrayBufferDetachedStateBase> arrayBufferState(this->CreateDetachedState(this->buffer, this->bufferLength));
216-
217215
this->buffer = nullptr;
218216
this->bufferLength = 0;
219217
this->isDetached = true;
@@ -235,7 +233,13 @@ namespace Js
235233
this->DetachBufferFromParent(item->Get());
236234
});
237235
}
236+
}
238237

238+
ArrayBufferDetachedStateBase* ArrayBuffer::DetachAndGetState()
239+
{
240+
// Save the state before detaching
241+
AutoPtr<ArrayBufferDetachedStateBase> arrayBufferState(this->CreateDetachedState(this->buffer, this->bufferLength));
242+
Detach();
239243
return arrayBufferState.Detach();
240244
}
241245

@@ -594,7 +598,7 @@ namespace Js
594598
else if (length > 0)
595599
{
596600
Recycler* recycler = GetType()->GetLibrary()->GetRecycler();
597-
if (recycler->ReportExternalMemoryAllocation(length))
601+
if (recycler->RequestExternalMemoryAllocation(length))
598602
{
599603
buffer = (BYTE*)allocator(length);
600604
if (buffer == nullptr)
@@ -607,7 +611,7 @@ namespace Js
607611
{
608612
recycler->CollectNow<CollectOnTypedArrayAllocation>();
609613

610-
if (recycler->ReportExternalMemoryAllocation(length))
614+
if (recycler->RequestExternalMemoryAllocation(length))
611615
{
612616
buffer = (BYTE*)allocator(length);
613617
if (buffer == nullptr)
@@ -909,10 +913,10 @@ namespace Js
909913
// Expanding buffer
910914
if (newBufferLength > this->bufferLength)
911915
{
912-
if (!recycler->ReportExternalMemoryAllocation(newBufferLength - this->bufferLength))
916+
if (!recycler->RequestExternalMemoryAllocation(newBufferLength - this->bufferLength))
913917
{
914918
recycler->CollectNow<CollectOnTypedArrayAllocation>();
915-
if (!recycler->ReportExternalMemoryAllocation(newBufferLength - this->bufferLength))
919+
if (!recycler->RequestExternalMemoryAllocation(newBufferLength - this->bufferLength))
916920
{
917921
reportFailureFn();
918922
}
@@ -1001,7 +1005,7 @@ namespace Js
10011005
WebAssemblyArrayBuffer* WebAssemblyArrayBuffer::Create(byte* buffer, uint32 length, DynamicType * type)
10021006
{
10031007
Recycler* recycler = type->GetScriptContext()->GetRecycler();
1004-
WebAssemblyArrayBuffer* result;
1008+
WebAssemblyArrayBuffer* result = nullptr;
10051009
if (buffer)
10061010
{
10071011
result = RecyclerNewFinalized(recycler, WebAssemblyArrayBuffer, buffer, length, type);
@@ -1018,9 +1022,10 @@ namespace Js
10181022
{
10191023
result = RecyclerNewFinalized(recycler, WebAssemblyArrayBuffer, length, type);
10201024
}
1025+
// Only add external memory when we create a new internal buffer
1026+
recycler->AddExternalMemoryUsage(length);
10211027
}
10221028
Assert(result);
1023-
recycler->AddExternalMemoryUsage(length);
10241029
return result;
10251030
}
10261031

@@ -1040,71 +1045,75 @@ namespace Js
10401045
Assert(UNREACHED);
10411046
JavascriptError::ThrowTypeError(GetScriptContext(), WASMERR_BufferGrowOnly);
10421047
}
1043-
uint32 growSize = newBufferLength - this->bufferLength;
10441048

1045-
bool failedReport = false;
1046-
const auto reportFailedFn = [&failedReport] { failedReport = true; };
1049+
uint32 growSize = newBufferLength - this->bufferLength;
1050+
const auto finalizeGrowMemory = [&](WebAssemblyArrayBuffer* newArrayBuffer)
1051+
{
1052+
AssertOrFailFast(newArrayBuffer && newArrayBuffer->GetByteLength() == newBufferLength);
1053+
// Detach the buffer from this ArrayBuffer
1054+
this->Detach();
1055+
return newArrayBuffer;
1056+
};
1057+
1058+
// We're not growing the buffer, just create a new WebAssemblyArrayBuffer and detach this
1059+
if (growSize == 0)
1060+
{
1061+
return finalizeGrowMemory(this->GetLibrary()->CreateWebAssemblyArrayBuffer(this->buffer, this->bufferLength));
1062+
}
10471063

1048-
WebAssemblyArrayBuffer* newArrayBuffer = nullptr;
10491064
#if ENABLE_FAST_ARRAYBUFFER
1065+
// 8Gb Array case
10501066
if (CONFIG_FLAG(WasmFastArray))
10511067
{
10521068
AssertOrFailFast(this->buffer);
1053-
ReportDifferentialAllocation(newBufferLength, reportFailedFn);
1054-
if (failedReport)
1069+
const auto virtualAllocFunc = [&]
10551070
{
1056-
return nullptr;
1057-
}
1058-
1059-
if (growSize > 0)
1071+
return !!VirtualAlloc(this->buffer + this->bufferLength, growSize, MEM_COMMIT, PAGE_READWRITE);
1072+
};
1073+
if (!this->GetRecycler()->DoExternalAllocation(growSize, virtualAllocFunc))
10601074
{
1061-
LPVOID newMem = VirtualAlloc(this->buffer + this->bufferLength, growSize, MEM_COMMIT, PAGE_READWRITE);
1062-
if (!newMem)
1063-
{
1064-
Recycler* recycler = this->GetRecycler();
1065-
recycler->ReportExternalMemoryFailure(newBufferLength);
1066-
return nullptr;
1067-
}
1075+
return nullptr;
10681076
}
1069-
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(this->buffer, newBufferLength);
1077+
return finalizeGrowMemory(this->GetLibrary()->CreateWebAssemblyArrayBuffer(this->buffer, newBufferLength));
10701078
}
1071-
else
10721079
#endif
1080+
1081+
// No previous buffer case
10731082
if (this->GetByteLength() == 0)
10741083
{
1075-
if (growSize > 0)
1076-
{
1077-
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(newBufferLength);
1078-
}
1079-
else
1080-
{
1081-
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(this->buffer, 0);
1082-
}
1084+
Assert(newBufferLength == growSize);
1085+
// Creating a new buffer will do the external memory allocation report
1086+
return finalizeGrowMemory(this->GetLibrary()->CreateWebAssemblyArrayBuffer(newBufferLength));
10831087
}
1084-
else
1088+
1089+
// Regular growing case
10851090
{
1086-
ReportDifferentialAllocation(newBufferLength, reportFailedFn);
1087-
if (failedReport)
1091+
// Disable Interrupts while doing a ReAlloc to minimize chances to end up in a bad state
1092+
AutoDisableInterrupt autoDisableInterrupt(this->GetScriptContext()->GetThreadContext(), false);
1093+
1094+
byte* newBuffer = nullptr;
1095+
const auto reallocFunc = [&]
10881096
{
1089-
return nullptr;
1090-
}
1091-
byte* newBuffer = ReallocZero(this->buffer, this->bufferLength, newBufferLength);
1092-
if (!newBuffer)
1097+
newBuffer = ReallocZero(this->buffer, this->bufferLength, newBufferLength);
1098+
if (newBuffer != nullptr)
1099+
{
1100+
// Realloc freed this->buffer
1101+
// if anything goes wrong before we detach, we can't recover the state and should failfast
1102+
autoDisableInterrupt.RequireExplicitCompletion();
1103+
}
1104+
return !!newBuffer;
1105+
};
1106+
1107+
if (!this->GetRecycler()->DoExternalAllocation(growSize, reallocFunc))
10931108
{
1094-
this->GetRecycler()->ReportExternalMemoryFailure(newBufferLength - this->bufferLength);
10951109
return nullptr;
10961110
}
1097-
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(newBuffer, newBufferLength);
1098-
}
10991111

1100-
if (!newArrayBuffer || newArrayBuffer->GetByteLength() != newBufferLength)
1101-
{
1102-
return nullptr;
1112+
WebAssemblyArrayBuffer* newArrayBuffer = finalizeGrowMemory(this->GetLibrary()->CreateWebAssemblyArrayBuffer(newBuffer, newBufferLength));
1113+
// We've successfully Detached this buffer and created a new WebAssemblyArrayBuffer
1114+
autoDisableInterrupt.Completed();
1115+
return newArrayBuffer;
11031116
}
1104-
1105-
AutoDiscardPTR<Js::ArrayBufferDetachedStateBase> state(DetachAndGetState());
1106-
state->MarkAsClaimed();
1107-
return newArrayBuffer;
11081117
}
11091118

11101119
ArrayBuffer * WebAssemblyArrayBuffer::TransferInternal(uint32 newBufferLength)

lib/Runtime/Library/ArrayBuffer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ namespace Js
159159
virtual BOOL GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext) override;
160160
virtual BOOL GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext) override;
161161

162-
virtual ArrayBufferDetachedStateBase* DetachAndGetState();
162+
ArrayBufferDetachedStateBase* DetachAndGetState();
163163
virtual uint32 GetByteLength() const override { return bufferLength; }
164164
virtual BYTE* GetBuffer() const override { return buffer; }
165165

@@ -185,6 +185,7 @@ namespace Js
185185

186186
virtual ArrayBuffer * TransferInternal(DECLSPEC_GUARD_OVERFLOW uint32 newBufferLength) = 0;
187187
protected:
188+
void Detach();
188189

189190
typedef void __cdecl FreeFn(void* ptr);
190191
virtual ArrayBufferDetachedStateBase* CreateDetachedState(BYTE* buffer, DECLSPEC_GUARD_OVERFLOW uint32 bufferLength) = 0;

lib/Runtime/Library/SharedArrayBuffer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ namespace Js
270270
}
271271
};
272272

273-
if (recycler->ReportExternalMemoryAllocation(length))
273+
if (recycler->RequestExternalMemoryAllocation(length))
274274
{
275275
buffer = alloc(length);
276276
if (buffer == nullptr)

0 commit comments

Comments
 (0)