Skip to content

Commit 5156c9b

Browse files
committed
Strengthen WebAssemblyArrayBuffer.GrowMemory
OS#12948195
1 parent a2ee304 commit 5156c9b

File tree

5 files changed

+96
-38
lines changed

5 files changed

+96
-38
lines changed

lib/Common/Memory/Recycler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1101,7 +1101,7 @@ Recycler::AddExternalMemoryUsage(size_t size)
11011101
CollectNow<CollectOnAllocation>();
11021102
}
11031103

1104-
BOOL Recycler::ReportExternalMemoryAllocation(size_t size)
1104+
bool Recycler::RequestExternalMemoryAllocation(size_t size)
11051105
{
11061106
return recyclerPageAllocator.RequestAlloc(size);
11071107
}

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) \
@@ -2525,3 +2528,35 @@ extern bool IsLikelyRuntimeFalseReference(
25252528
#else
25262529
#define DECLARE_RECYCLER_VERIFY_MARK_FRIEND()
25272530
#endif
2531+
2532+
template <typename ExternalAllocFunc>
2533+
bool Recycler::DoExternalAllocation(size_t size, ExternalAllocFunc externalAllocFunc)
2534+
{
2535+
// Request external memory allocation
2536+
if (!RequestExternalMemoryAllocation(size))
2537+
{
2538+
// Attempt to free some memory then try again
2539+
CollectNow<CollectOnTypedArrayAllocation>();
2540+
if (!RequestExternalMemoryAllocation(size))
2541+
{
2542+
return false;
2543+
}
2544+
}
2545+
struct AutoExternalAllocation
2546+
{
2547+
bool allocationSucceeded = false;
2548+
Recycler* recycler;
2549+
size_t size;
2550+
AutoExternalAllocation(Recycler* recycler, size_t size): recycler(recycler), size(size) {}
2551+
// In case the externalAllocFunc throws or fails, the destructor will report the failure
2552+
~AutoExternalAllocation() { if (!allocationSucceeded) recycler->ReportExternalMemoryFailure(size); }
2553+
};
2554+
AutoExternalAllocation externalAllocation(this, size);
2555+
if (externalAllocFunc())
2556+
{
2557+
this->AddExternalMemoryUsage(size);
2558+
externalAllocation.allocationSucceeded = true;
2559+
return true;
2560+
}
2561+
return false;
2562+
}

lib/Runtime/Library/ArrayBuffer.cpp

Lines changed: 56 additions & 34 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

@@ -1042,68 +1047,85 @@ namespace Js
10421047
}
10431048
uint32 growSize = newBufferLength - this->bufferLength;
10441049

1045-
bool failedReport = false;
1046-
const auto reportFailedFn = [&failedReport] { failedReport = true; };
1050+
struct AutoFailFastOnBadState
1051+
{
1052+
bool isStateValid = true;
1053+
~AutoFailFastOnBadState()
1054+
{
1055+
if (!isStateValid)
1056+
{
1057+
// We ended up in an unrecoverable state, fail fast
1058+
Js::Throw::FatalInternalError();
1059+
}
1060+
}
1061+
};
1062+
AutoFailFastOnBadState autoFailFastOnBadState;
10471063

10481064
WebAssemblyArrayBuffer* newArrayBuffer = nullptr;
10491065
#if ENABLE_FAST_ARRAYBUFFER
10501066
if (CONFIG_FLAG(WasmFastArray))
10511067
{
10521068
AssertOrFailFast(this->buffer);
1053-
ReportDifferentialAllocation(newBufferLength, reportFailedFn);
1054-
if (failedReport)
1055-
{
1056-
return nullptr;
1057-
}
1058-
10591069
if (growSize > 0)
10601070
{
1061-
LPVOID newMem = VirtualAlloc(this->buffer + this->bufferLength, growSize, MEM_COMMIT, PAGE_READWRITE);
1062-
if (!newMem)
1071+
const auto virtualAllocFunc = [&]
1072+
{
1073+
return !!VirtualAlloc(this->buffer + this->bufferLength, growSize, MEM_COMMIT, PAGE_READWRITE);
1074+
};
1075+
if (!this->GetRecycler()->DoExternalAllocation(growSize, virtualAllocFunc))
10631076
{
1064-
Recycler* recycler = this->GetRecycler();
1065-
recycler->ReportExternalMemoryFailure(newBufferLength);
10661077
return nullptr;
10671078
}
10681079
}
1069-
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(this->buffer, newBufferLength);
1080+
newArrayBuffer = this->GetLibrary()->CreateWebAssemblyArrayBuffer(this->buffer, newBufferLength);
10701081
}
10711082
else
10721083
#endif
10731084
if (this->GetByteLength() == 0)
10741085
{
10751086
if (growSize > 0)
10761087
{
1077-
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(newBufferLength);
1088+
// Creating a new buffer will do the external memory allocation report
1089+
newArrayBuffer = this->GetLibrary()->CreateWebAssemblyArrayBuffer(newBufferLength);
10781090
}
10791091
else
10801092
{
1081-
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(this->buffer, 0);
1093+
newArrayBuffer = this->GetLibrary()->CreateWebAssemblyArrayBuffer(this->buffer, 0);
10821094
}
10831095
}
10841096
else
10851097
{
1086-
ReportDifferentialAllocation(newBufferLength, reportFailedFn);
1087-
if (failedReport)
1098+
byte* newBuffer = nullptr;
1099+
const auto reallocFunc = [&]
10881100
{
1089-
return nullptr;
1090-
}
1091-
byte* newBuffer = ReallocZero(this->buffer, this->bufferLength, newBufferLength);
1092-
if (!newBuffer)
1101+
newBuffer = ReallocZero(this->buffer, this->bufferLength, newBufferLength);
1102+
// Realloc freed this->buffer if newBuffer is not nullptr
1103+
// if anything goes wrong before we detach, we can't recover the state and should failfast
1104+
autoFailFastOnBadState.isStateValid = newBuffer == nullptr;
1105+
return !!newBuffer;
1106+
};
1107+
1108+
if (!this->GetRecycler()->DoExternalAllocation(growSize, reallocFunc))
10931109
{
1094-
this->GetRecycler()->ReportExternalMemoryFailure(newBufferLength - this->bufferLength);
10951110
return nullptr;
10961111
}
1097-
newArrayBuffer = GetLibrary()->CreateWebAssemblyArrayBuffer(newBuffer, newBufferLength);
1112+
1113+
this->buffer = nullptr;
1114+
newArrayBuffer = this->GetLibrary()->CreateWebAssemblyArrayBuffer(newBuffer, newBufferLength);
10981115
}
10991116

11001117
if (!newArrayBuffer || newArrayBuffer->GetByteLength() != newBufferLength)
11011118
{
11021119
return nullptr;
11031120
}
11041121

1105-
AutoDiscardPTR<Js::ArrayBufferDetachedStateBase> state(DetachAndGetState());
1106-
state->MarkAsClaimed();
1122+
// Detach the buffer from this ArrayBuffer
1123+
this->Detach();
1124+
1125+
// The buffer has been freed or passed down to the new ArrayBuffer.
1126+
Assert(this->buffer == nullptr);
1127+
autoFailFastOnBadState.isStateValid = true;
1128+
11071129
return newArrayBuffer;
11081130
}
11091131

lib/Runtime/Library/ArrayBuffer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ 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+
void Detach();
163+
ArrayBufferDetachedStateBase* DetachAndGetState();
163164
virtual uint32 GetByteLength() const override { return bufferLength; }
164165
virtual BYTE* GetBuffer() const override { return buffer; }
165166

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)