Commit e6d13999 authored by gdeepti's avatar gdeepti Committed by Commit bot

[wasm] Memory buffer should be detached after Memory.Grow

Memory.Grow should detach the ArrayBuffer associated with the Mem object after Grow. Currently, when guard pages are enabled protection is changed to make more of the buffer accessible. This does not work for when the buffer should be detached after grow, because the memory object has a reference to the same buffer befor/after grow.

R=titzer@chromium.org, eholk@chromium.org

Review-Url: https://codereview.chromium.org/2653183003
Cr-Commit-Position: refs/heads/master@{#42717}
parent 373320ae
...@@ -61,12 +61,15 @@ static void MemoryFinalizer(const v8::WeakCallbackInfo<void>& data) { ...@@ -61,12 +61,15 @@ static void MemoryFinalizer(const v8::WeakCallbackInfo<void>& data) {
JSArrayBuffer** p = reinterpret_cast<JSArrayBuffer**>(data.GetParameter()); JSArrayBuffer** p = reinterpret_cast<JSArrayBuffer**>(data.GetParameter());
JSArrayBuffer* buffer = *p; JSArrayBuffer* buffer = *p;
void* memory = buffer->backing_store(); if (!buffer->was_neutered()) {
base::OS::Free(memory, void* memory = buffer->backing_store();
RoundUp(kWasmMaxHeapOffset, base::OS::CommitPageSize())); DCHECK(memory != nullptr);
base::OS::Free(memory,
RoundUp(kWasmMaxHeapOffset, base::OS::CommitPageSize()));
data.GetIsolate()->AdjustAmountOfExternalAllocatedMemory( data.GetIsolate()->AdjustAmountOfExternalAllocatedMemory(
-buffer->byte_length()->Number()); -buffer->byte_length()->Number());
}
GlobalHandles::Destroy(reinterpret_cast<Object**>(p)); GlobalHandles::Destroy(reinterpret_cast<Object**>(p));
} }
...@@ -756,6 +759,28 @@ Handle<Script> CreateWasmScript(Isolate* isolate, ...@@ -756,6 +759,28 @@ Handle<Script> CreateWasmScript(Isolate* isolate,
} }
} // namespace } // namespace
Handle<JSArrayBuffer> SetupArrayBuffer(Isolate* isolate, void* backing_store,
size_t size, bool is_external,
bool enable_guard_regions) {
Handle<JSArrayBuffer> buffer = isolate->factory()->NewJSArrayBuffer();
JSArrayBuffer::Setup(buffer, isolate, is_external, backing_store,
static_cast<int>(size));
buffer->set_is_neuterable(false);
buffer->set_has_guard_region(enable_guard_regions);
if (is_external) {
// We mark the buffer as external if we allocated it here with guard
// pages. That means we need to arrange for it to be freed.
// TODO(eholk): Finalizers may not run when the main thread is shutting
// down, which means we may leak memory here.
Handle<Object> global_handle = isolate->global_handles()->Create(*buffer);
GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(),
&MemoryFinalizer, v8::WeakCallbackType::kFinalizer);
}
return buffer;
}
Handle<JSArrayBuffer> wasm::NewArrayBuffer(Isolate* isolate, size_t size, Handle<JSArrayBuffer> wasm::NewArrayBuffer(Isolate* isolate, size_t size,
bool enable_guard_regions) { bool enable_guard_regions) {
if (size > (FLAG_wasm_max_mem_pages * WasmModule::kPageSize)) { if (size > (FLAG_wasm_max_mem_pages * WasmModule::kPageSize)) {
...@@ -781,24 +806,8 @@ Handle<JSArrayBuffer> wasm::NewArrayBuffer(Isolate* isolate, size_t size, ...@@ -781,24 +806,8 @@ Handle<JSArrayBuffer> wasm::NewArrayBuffer(Isolate* isolate, size_t size,
} }
#endif #endif
Handle<JSArrayBuffer> buffer = isolate->factory()->NewJSArrayBuffer(); return SetupArrayBuffer(isolate, memory, size, is_external,
JSArrayBuffer::Setup(buffer, isolate, is_external, memory, enable_guard_regions);
static_cast<int>(size));
buffer->set_is_neuterable(false);
buffer->set_has_guard_region(enable_guard_regions);
if (is_external) {
// We mark the buffer as external if we allocated it here with guard
// pages. That means we need to arrange for it to be freed.
// TODO(eholk): Finalizers may not run when the main thread is shutting
// down, which means we may leak memory here.
Handle<Object> global_handle = isolate->global_handles()->Create(*buffer);
GlobalHandles::MakeWeak(global_handle.location(), global_handle.location(),
&MemoryFinalizer, v8::WeakCallbackType::kFinalizer);
}
return buffer;
} }
const char* wasm::SectionName(WasmSectionCode code) { const char* wasm::SectionName(WasmSectionCode code) {
...@@ -2349,25 +2358,16 @@ Handle<JSArrayBuffer> GrowMemoryBuffer(Isolate* isolate, ...@@ -2349,25 +2358,16 @@ Handle<JSArrayBuffer> GrowMemoryBuffer(Isolate* isolate,
return Handle<JSArrayBuffer>::null(); return Handle<JSArrayBuffer>::null();
} }
Handle<JSArrayBuffer> new_buffer; // TODO(gdeepti): Change the protection here instead of allocating a new
if (!old_buffer.is_null() && old_buffer->has_guard_region()) { // buffer before guard regions are turned on, see issue #5886.
// We don't move the backing store, we simply change the protection to make const bool enable_guard_regions =
// more of it accessible. !old_buffer.is_null() && old_buffer->has_guard_region();
base::OS::Unprotect(old_buffer->backing_store(), new_size); Handle<JSArrayBuffer> new_buffer =
reinterpret_cast<v8::Isolate*>(isolate) NewArrayBuffer(isolate, new_size, enable_guard_regions);
->AdjustAmountOfExternalAllocatedMemory(pages * WasmModule::kPageSize); if (new_buffer.is_null()) return new_buffer;
Handle<Object> new_size_object = Address new_mem_start = static_cast<Address>(new_buffer->backing_store());
isolate->factory()->NewNumberFromSize(new_size); if (old_size != 0) {
old_buffer->set_byte_length(*new_size_object); memcpy(new_mem_start, old_mem_start, old_size);
new_buffer = old_buffer;
} else {
const bool enable_guard_regions = false;
new_buffer = NewArrayBuffer(isolate, new_size, enable_guard_regions);
if (new_buffer.is_null()) return new_buffer;
Address new_mem_start = static_cast<Address>(new_buffer->backing_store());
if (old_size != 0) {
memcpy(new_mem_start, old_mem_start, old_size);
}
} }
return new_buffer; return new_buffer;
} }
...@@ -2387,6 +2387,30 @@ void UncheckedUpdateInstanceMemory(Isolate* isolate, ...@@ -2387,6 +2387,30 @@ void UncheckedUpdateInstanceMemory(Isolate* isolate,
old_mem_start, new_mem_start, old_size, new_size); old_mem_start, new_mem_start, old_size, new_size);
} }
void DetachArrayBuffer(Isolate* isolate, Handle<JSArrayBuffer> buffer) {
const bool has_guard_regions =
(!buffer.is_null() && buffer->has_guard_region());
void* backing_store = buffer->backing_store();
if (backing_store != nullptr) {
DCHECK(!buffer->is_neuterable());
int64_t byte_length = NumberToSize(buffer->byte_length());
buffer->set_is_neuterable(true);
if (!has_guard_regions) {
buffer->set_is_external(true);
isolate->heap()->UnregisterArrayBuffer(*buffer);
}
buffer->Neuter();
if (!has_guard_regions) {
isolate->array_buffer_allocator()->Free(backing_store, byte_length);
} else {
base::OS::Free(backing_store, RoundUp(i::wasm::kWasmMaxHeapOffset,
base::OS::CommitPageSize()));
reinterpret_cast<v8::Isolate*>(isolate)
->AdjustAmountOfExternalAllocatedMemory(-byte_length);
}
}
}
int32_t wasm::GrowWebAssemblyMemory(Isolate* isolate, int32_t wasm::GrowWebAssemblyMemory(Isolate* isolate,
Handle<WasmMemoryObject> receiver, Handle<WasmMemoryObject> receiver,
uint32_t pages) { uint32_t pages) {
...@@ -2402,12 +2426,26 @@ int32_t wasm::GrowWebAssemblyMemory(Isolate* isolate, ...@@ -2402,12 +2426,26 @@ int32_t wasm::GrowWebAssemblyMemory(Isolate* isolate,
old_size = old_buffer->byte_length()->Number(); old_size = old_buffer->byte_length()->Number();
old_mem_start = static_cast<Address>(old_buffer->backing_store()); old_mem_start = static_cast<Address>(old_buffer->backing_store());
} }
Handle<JSArrayBuffer> new_buffer;
// Return current size if grow by 0 // Return current size if grow by 0
if (pages == 0) { if (pages == 0) {
if (!old_buffer.is_null() && old_buffer->backing_store() != nullptr) {
new_buffer = SetupArrayBuffer(isolate, old_buffer->backing_store(),
old_size, old_buffer->is_external(),
old_buffer->has_guard_region());
memory_object->set_buffer(*new_buffer);
old_buffer->set_is_neuterable(true);
if (!old_buffer->has_guard_region()) {
old_buffer->set_is_external(true);
isolate->heap()->UnregisterArrayBuffer(*old_buffer);
}
// Neuter but don't free the memory because it is now being used by
// new_buffer.
old_buffer->Neuter();
}
DCHECK(old_size % WasmModule::kPageSize == 0); DCHECK(old_size % WasmModule::kPageSize == 0);
return (old_size / WasmModule::kPageSize); return (old_size / WasmModule::kPageSize);
} }
Handle<JSArrayBuffer> new_buffer;
if (!memory_object->has_instances_link()) { if (!memory_object->has_instances_link()) {
// Memory object does not have an instance associated with it, just grow // Memory object does not have an instance associated with it, just grow
uint32_t max_pages; uint32_t max_pages;
...@@ -2444,6 +2482,7 @@ int32_t wasm::GrowWebAssemblyMemory(Isolate* isolate, ...@@ -2444,6 +2482,7 @@ int32_t wasm::GrowWebAssemblyMemory(Isolate* isolate,
} }
} }
memory_object->set_buffer(*new_buffer); memory_object->set_buffer(*new_buffer);
DetachArrayBuffer(isolate, old_buffer);
DCHECK(old_size % WasmModule::kPageSize == 0); DCHECK(old_size % WasmModule::kPageSize == 0);
return (old_size / WasmModule::kPageSize); return (old_size / WasmModule::kPageSize);
} }
......
...@@ -397,3 +397,17 @@ load("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -397,3 +397,17 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
// no maximum // no maximum
assertThrows(() => builder.instantiate({m: {m: new WebAssembly.Memory({initial: 1})}})); assertThrows(() => builder.instantiate({m: {m: new WebAssembly.Memory({initial: 1})}}));
})(); })();
(function TestMemoryGrowDetachBuffer() {
print("TestMemoryGrowDetachBuffer");
let memory = new WebAssembly.Memory({initial: 1, maximum: 5});
var builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "imported_mem");
let instance = builder.instantiate({m: {imported_mem: memory}});
let buffer = memory.buffer;
assertEquals(kPageSize, buffer.byteLength);
assertEquals(1, memory.grow(2));
assertTrue(buffer !== memory.buffer);
assertEquals(0, buffer.byteLength);
assertEquals(3*kPageSize, memory.buffer.byteLength);
})();
...@@ -454,14 +454,13 @@ var mem = new Memory({initial:1, maximum:2}); ...@@ -454,14 +454,13 @@ var mem = new Memory({initial:1, maximum:2});
var buf = mem.buffer; var buf = mem.buffer;
assertEq(buf.byteLength, kPageSize); assertEq(buf.byteLength, kPageSize);
assertEq(mem.grow(0), 1); assertEq(mem.grow(0), 1);
// TODO(gdeepti): Pending spec clarification assertTrue(buf !== mem.buffer);
// assertTrue(buf !== mem.buffer); assertEq(buf.byteLength, 0);
// assertEq(buf.byteLength, 0);
buf = mem.buffer; buf = mem.buffer;
assertEq(buf.byteLength, kPageSize); assertEq(buf.byteLength, kPageSize);
assertEq(mem.grow(1), 1); assertEq(mem.grow(1), 1);
// TODO(gdeepti): assertTrue(buf !== mem.buffer); assertTrue(buf !== mem.buffer);
// TODO(gdeepti): assertEq(buf.byteLength, 0); assertEq(buf.byteLength, 0);
buf = mem.buffer; buf = mem.buffer;
assertEq(buf.byteLength, 2 * kPageSize); assertEq(buf.byteLength, 2 * kPageSize);
assertErrorMessage(() => mem.grow(1), Error, /failed to grow memory/); assertErrorMessage(() => mem.grow(1), Error, /failed to grow memory/);
......
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