Commit dfdc0e6d authored by Deepti Gandluri's avatar Deepti Gandluri Committed by Commit Bot

[wasm] Reserve upto maximum for a shared WebAssembly.Memory

When using a shared WebAssembly.Memory, always try to reserve up to the
maximum to avoid having to move the buffer. If after multiple retries
it is not possible to reserve the maximum, fall back to initial size
reservation.

 - Add new methods to allocate a Shared WebAssemblyMemory.buffer
 - Use these to reserve upto the mazimum for a Shared WebAssembly.Memory
 - Cleanup js-api so actual allocation is done inside the constructor

BUG: v8:8564
Change-Id: I97815c7c94a2b84416cd867fb23b3c815d7f0f12
Reviewed-on: https://chromium-review.googlesource.com/c/1480910Reviewed-by: 's avatarBen Smith <binji@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Commit-Queue: Deepti Gandluri <gdeepti@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59805}
parent 5d0f5bd7
......@@ -187,7 +187,8 @@ class InstanceBuilder {
void InitGlobals();
// Allocate memory for a module instance as a new JSArrayBuffer.
Handle<JSArrayBuffer> AllocateMemory(uint32_t num_pages);
Handle<JSArrayBuffer> AllocateMemory(uint32_t initial_pages,
uint32_t maximum_pages);
bool NeedsWrappers() const;
......@@ -280,7 +281,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
// even when the size is zero to prevent null-dereference issues
// (e.g. https://crbug.com/769637).
// Allocate memory if the initial size is more than 0 pages.
memory_ = AllocateMemory(initial_pages);
memory_ = AllocateMemory(initial_pages, module_->maximum_pages);
if (memory_.is_null()) {
// failed to allocate memory
DCHECK(isolate_->has_pending_exception() || thrower_->error());
......@@ -1213,19 +1214,26 @@ void InstanceBuilder::InitGlobals() {
}
// Allocate memory for a module instance as a new JSArrayBuffer.
Handle<JSArrayBuffer> InstanceBuilder::AllocateMemory(uint32_t num_pages) {
if (num_pages > max_mem_pages()) {
Handle<JSArrayBuffer> InstanceBuilder::AllocateMemory(uint32_t initial_pages,
uint32_t maximum_pages) {
if (initial_pages > max_mem_pages()) {
thrower_->RangeError("Out of memory: wasm memory too large");
return Handle<JSArrayBuffer>::null();
}
const bool is_shared_memory = module_->has_shared_memory && enabled_.threads;
SharedFlag shared_flag =
is_shared_memory ? SharedFlag::kShared : SharedFlag::kNotShared;
Handle<JSArrayBuffer> mem_buffer;
if (!NewArrayBuffer(isolate_, num_pages * kWasmPageSize, shared_flag)
if (is_shared_memory) {
if (!NewSharedArrayBuffer(isolate_, initial_pages * kWasmPageSize,
maximum_pages * kWasmPageSize)
.ToHandle(&mem_buffer)) {
thrower_->RangeError("Out of memory: wasm shared memory");
}
} else {
if (!NewArrayBuffer(isolate_, initial_pages * kWasmPageSize)
.ToHandle(&mem_buffer)) {
thrower_->RangeError("Out of memory: wasm memory");
}
}
return mem_buffer;
}
......
......@@ -1102,29 +1102,30 @@ void WebAssemblyMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
if (is_shared_memory && maximum == -1) {
thrower.TypeError(
"If shared is true, maximum property should be defined.");
return;
}
}
i::SharedFlag shared_flag =
is_shared_memory ? i::SharedFlag::kShared : i::SharedFlag::kNotShared;
i::Handle<i::JSArrayBuffer> buffer;
size_t size = static_cast<size_t>(i::wasm::kWasmPageSize) *
static_cast<size_t>(initial);
if (!i::wasm::NewArrayBuffer(i_isolate, size, shared_flag)
.ToHandle(&buffer)) {
i::Handle<i::JSObject> memory_obj;
if (!i::WasmMemoryObject::New(i_isolate, static_cast<uint32_t>(initial),
static_cast<uint32_t>(maximum),
is_shared_memory)
.ToHandle(&memory_obj)) {
thrower.RangeError("could not allocate memory");
return;
}
if (buffer->is_shared()) {
if (is_shared_memory) {
i::Handle<i::JSArrayBuffer> buffer(
i::Handle<i::WasmMemoryObject>::cast(memory_obj)->array_buffer(),
i_isolate);
Maybe<bool> result =
buffer->SetIntegrityLevel(buffer, i::FROZEN, i::kDontThrow);
if (!result.FromJust()) {
thrower.TypeError(
"Status of setting SetIntegrityLevel of buffer is false.");
return;
}
}
i::Handle<i::JSObject> memory_obj = i::WasmMemoryObject::New(
i_isolate, buffer, static_cast<uint32_t>(maximum));
args.GetReturnValue().Set(Utils::ToLocal(memory_obj));
}
......
......@@ -27,8 +27,19 @@ void AddAllocationStatusSample(Isolate* isolate,
static_cast<int>(status));
}
size_t GetAllocationLength(uint32_t size, bool require_full_guard_regions) {
if (require_full_guard_regions) {
return RoundUp(kWasmMaxHeapOffset + kNegativeGuardSize, CommitPageSize());
} else {
return RoundUp(
base::bits::RoundUpToPowerOfTwo32(static_cast<uint32_t>(size)),
kWasmPageSize);
}
}
void* TryAllocateBackingStore(WasmMemoryTracker* memory_tracker, Heap* heap,
size_t size, void** allocation_base,
size_t size, size_t max_size,
void** allocation_base,
size_t* allocation_length) {
using AllocationStatus = WasmMemoryTracker::AllocationStatus;
#if V8_TARGET_ARCH_64_BIT
......@@ -42,6 +53,10 @@ void* TryAllocateBackingStore(WasmMemoryTracker* memory_tracker, Heap* heap,
// require two GCs because the first GC maybe incremental and may have
// floating garbage.
static constexpr int kAllocationRetries = 2;
// TODO(7881): do not use static_cast<uint32_t>() here
uint32_t reservation_size =
static_cast<uint32_t>((max_size > size) ? max_size : size);
// TODO(8898): Cleanup the allocation retry flow
bool did_retry = false;
for (int trial = 0;; ++trial) {
// For guard regions, we always allocate the largest possible offset into
......@@ -50,13 +65,8 @@ void* TryAllocateBackingStore(WasmMemoryTracker* memory_tracker, Heap* heap,
//
// To protect against 32-bit integer overflow issues, we also protect the
// 2GiB before the valid part of the memory buffer.
// TODO(7881): do not use static_cast<uint32_t>() here
*allocation_length =
require_full_guard_regions
? RoundUp(kWasmMaxHeapOffset + kNegativeGuardSize, CommitPageSize())
: RoundUp(base::bits::RoundUpToPowerOfTwo32(
static_cast<uint32_t>(size)),
kWasmPageSize);
GetAllocationLength(reservation_size, require_full_guard_regions);
DCHECK_GE(*allocation_length, size);
DCHECK_GE(*allocation_length, kWasmPageSize);
......@@ -67,6 +77,11 @@ void* TryAllocateBackingStore(WasmMemoryTracker* memory_tracker, Heap* heap,
did_retry = true;
// After first and second GC: retry.
if (trial == kAllocationRetries) {
// Always reset reservation_size to initial size so that at least the
// initial size can be allocated if maximum size reservation is not
// possible.
reservation_size = static_cast<uint32_t>(size);
// If we fail to allocate guard regions and the fallback is enabled, then
// retry without full guard regions.
if (require_full_guard_regions && FLAG_wasm_trap_handler_fallback) {
......@@ -157,7 +172,7 @@ WasmMemoryTracker::~WasmMemoryTracker() {
void* WasmMemoryTracker::TryAllocateBackingStoreForTesting(
Heap* heap, size_t size, void** allocation_base,
size_t* allocation_length) {
return TryAllocateBackingStore(this, heap, size, allocation_base,
return TryAllocateBackingStore(this, heap, size, size, allocation_base,
allocation_length);
}
......@@ -290,7 +305,9 @@ Handle<JSArrayBuffer> SetupArrayBuffer(Isolate* isolate, void* backing_store,
return buffer;
}
MaybeHandle<JSArrayBuffer> NewArrayBuffer(Isolate* isolate, size_t size,
MaybeHandle<JSArrayBuffer> AllocateAndSetupArrayBuffer(Isolate* isolate,
size_t size,
size_t maximum_size,
SharedFlag shared) {
// Enforce flag-limited maximum allocation size.
if (size > max_mem_bytes()) return {};
......@@ -302,7 +319,8 @@ MaybeHandle<JSArrayBuffer> NewArrayBuffer(Isolate* isolate, size_t size,
size_t allocation_length = 0;
void* memory = TryAllocateBackingStore(memory_tracker, isolate->heap(), size,
&allocation_base, &allocation_length);
maximum_size, &allocation_base,
&allocation_length);
if (memory == nullptr) return {};
#if DEBUG
......@@ -320,6 +338,18 @@ MaybeHandle<JSArrayBuffer> NewArrayBuffer(Isolate* isolate, size_t size,
return SetupArrayBuffer(isolate, memory, size, is_external, shared);
}
MaybeHandle<JSArrayBuffer> NewArrayBuffer(Isolate* isolate, size_t size) {
return AllocateAndSetupArrayBuffer(isolate, size, size,
SharedFlag::kNotShared);
}
MaybeHandle<JSArrayBuffer> NewSharedArrayBuffer(Isolate* isolate,
size_t initial_size,
size_t max_size) {
return AllocateAndSetupArrayBuffer(isolate, initial_size, max_size,
SharedFlag::kShared);
}
void DetachMemoryBuffer(Isolate* isolate, Handle<JSArrayBuffer> buffer,
bool free_memory) {
if (buffer->is_shared()) return; // Detaching shared buffers is impossible.
......
......@@ -142,8 +142,14 @@ class WasmMemoryTracker {
// Attempts to allocate an array buffer with guard regions suitable for trap
// handling. If address space is not available, it will return a buffer with
// mini-guards that will require bounds checks.
MaybeHandle<JSArrayBuffer> NewArrayBuffer(
Isolate*, size_t size, SharedFlag shared = SharedFlag::kNotShared);
MaybeHandle<JSArrayBuffer> NewArrayBuffer(Isolate*, size_t size);
// Attempts to allocate a SharedArrayBuffer with guard regions suitable for
// trap handling. If address space is not available, it will try to reserve
// up to the maximum for that memory. If all else fails, it will return a
// buffer with mini-guards of initial size.
MaybeHandle<JSArrayBuffer> NewSharedArrayBuffer(Isolate*, size_t initial_size,
size_t max_size);
Handle<JSArrayBuffer> SetupArrayBuffer(
Isolate*, void* backing_store, size_t size, bool is_external,
......
......@@ -1012,6 +1012,28 @@ Handle<WasmMemoryObject> WasmMemoryObject::New(
return memory_obj;
}
MaybeHandle<WasmMemoryObject> WasmMemoryObject::New(Isolate* isolate,
uint32_t initial,
uint32_t maximum,
bool is_shared_memory) {
Handle<JSArrayBuffer> buffer;
size_t size = static_cast<size_t>(i::wasm::kWasmPageSize) *
static_cast<size_t>(initial);
if (is_shared_memory) {
size_t max_size = static_cast<size_t>(i::wasm::kWasmPageSize) *
static_cast<size_t>(maximum);
if (!i::wasm::NewSharedArrayBuffer(isolate, size, max_size)
.ToHandle(&buffer)) {
return {};
}
} else {
if (!i::wasm::NewArrayBuffer(isolate, size).ToHandle(&buffer)) {
return {};
}
}
return New(isolate, buffer, maximum);
}
bool WasmMemoryObject::has_full_guard_region(Isolate* isolate) {
const wasm::WasmMemoryTracker::AllocationData* allocation =
isolate->wasm_engine()->memory_tracker()->FindAllocationData(
......
......@@ -326,6 +326,10 @@ class WasmMemoryObject : public JSObject {
V8_EXPORT_PRIVATE static Handle<WasmMemoryObject> New(
Isolate* isolate, MaybeHandle<JSArrayBuffer> buffer, uint32_t maximum);
V8_EXPORT_PRIVATE static MaybeHandle<WasmMemoryObject> New(
Isolate* isolate, uint32_t initial, uint32_t maximum,
bool is_shared_memory);
static int32_t Grow(Isolate*, Handle<WasmMemoryObject>, uint32_t pages);
OBJECT_CONSTRUCTORS(WasmMemoryObject, JSObject);
......
......@@ -854,8 +854,7 @@ TEST(Run_WasmModule_Reclaim_Memory) {
Handle<JSArrayBuffer> buffer;
for (int i = 0; i < 256; ++i) {
HandleScope scope(isolate);
CHECK(NewArrayBuffer(isolate, kWasmPageSize, SharedFlag::kNotShared)
.ToHandle(&buffer));
CHECK(NewArrayBuffer(isolate, kWasmPageSize).ToHandle(&buffer));
}
}
#endif
......
......@@ -66,9 +66,16 @@ byte* TestingModuleBuilder::AddMemory(uint32_t size, SharedFlag shared) {
DCHECK_IMPLIES(test_module_->origin == kWasmOrigin,
size % kWasmPageSize == 0);
test_module_->has_memory = true;
uint32_t max_size =
(test_module_->maximum_pages != 0) ? test_module_->maximum_pages : size;
uint32_t alloc_size = RoundUp(size, kWasmPageSize);
Handle<JSArrayBuffer> new_buffer;
CHECK(NewArrayBuffer(isolate_, alloc_size, shared).ToHandle(&new_buffer));
if (shared == SharedFlag::kShared) {
CHECK(NewSharedArrayBuffer(isolate_, alloc_size, max_size)
.ToHandle(&new_buffer));
} else {
CHECK(NewArrayBuffer(isolate_, alloc_size).ToHandle(&new_buffer));
}
CHECK(!new_buffer.is_null());
mem_start_ = reinterpret_cast<byte*>(new_buffer->backing_store());
mem_size_ = size;
......@@ -76,9 +83,8 @@ byte* TestingModuleBuilder::AddMemory(uint32_t size, SharedFlag shared) {
memset(mem_start_, 0, size);
// Create the WasmMemoryObject.
Handle<WasmMemoryObject> memory_object = WasmMemoryObject::New(
isolate_, new_buffer,
(test_module_->maximum_pages != 0) ? test_module_->maximum_pages : -1);
Handle<WasmMemoryObject> memory_object =
WasmMemoryObject::New(isolate_, new_buffer, max_size);
instance_object_->set_memory_object(*memory_object);
WasmMemoryObject::AddInstance(isolate_, memory_object, instance_object_);
// TODO(wasm): Delete the following two lines when test-run-wasm will use a
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment