From 1fc62304244644abe3e909eb03ea41a5ed1b192c Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 26 Jul 2019 16:17:29 +0200 Subject: [PATCH 01/11] Enable host-provided memory backing stores --- Makefile | 3 + example/mmap.cc | 367 ++++++++++++++++++ example/mmap.wasm | Bin 0 -> 128 bytes example/mmap.wat | 9 + include/wasm-v8.hh | 31 ++ ...2-wasm-objects-add-host-owned-memory.patch | 136 +++++++ src/wasm-v8-lowlevel.cc | 28 ++ src/wasm-v8-lowlevel.hh | 9 + src/wasm-v8.cc | 48 +++ 9 files changed, 631 insertions(+) create mode 100644 example/mmap.cc create mode 100644 example/mmap.wasm create mode 100644 example/mmap.wat create mode 100644 include/wasm-v8.hh create mode 100644 patch/0002-wasm-objects-add-host-owned-memory.patch diff --git a/Makefile b/Makefile index c610df8..678b233 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,8 @@ OUT_DIR = out # Example config EXAMPLE_OUT = ${OUT_DIR}/${EXAMPLE_DIR} EXAMPLES = \ + mmap +BLA = \ hello \ callback \ trap \ @@ -38,6 +40,7 @@ EXAMPLES = \ finalize \ serialize \ threads \ + mmap \ # multi \ # Wasm config diff --git a/example/mmap.cc b/example/mmap.cc new file mode 100644 index 0000000..4840c34 --- /dev/null +++ b/example/mmap.cc @@ -0,0 +1,367 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "wasm.hh" +#include "wasm-v8.hh" + + +static const char data_file[] = "mmap.data"; + +int mem_count = 0; + +auto open_mem_file(int pages) -> int { + // Create memory file. + std::cout << "opening memory file..." << std::endl; + auto size = pages * wasm::Memory::page_size; + + auto fd = open(data_file, O_RDWR | O_CREAT, 0600); + if (fd == -1) { + std::cout << "> Error opening memory file! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + exit(1); + } + if (ftruncate(fd, size) == -1) { + close(fd); + std::cout << "> Error initialising memory file! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + exit(1); + } + + return fd; +} + +struct mem_info { + void* base; + byte_t* data; + size_t alloc_size; + int fd; +}; + +auto make_mem(size_t size, int fd) -> std::unique_ptr { + std::cout << "> Making memory " + << "(size = " << size << ", fd = " << fd << ")..." << std::endl; + auto offset = wasm::v8::Memory::redzone_size_lo(size); + auto alloc_size = size + wasm::v8::Memory::redzone_size_hi(size); + + void* base = nullptr; + if (offset > 0) { + base = mmap(nullptr, offset, PROT_NONE, MAP_ANON | MAP_PRIVATE, 0, 0); + if (base == MAP_FAILED) { + std::cout << "> Error reserving lo redzone! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + exit(1); + } + } + + auto data = static_cast(base) + offset; + auto result = mmap( + data, alloc_size, PROT_NONE, MAP_FILE | MAP_FIXED | MAP_SHARED, fd, 0); + if (result == MAP_FAILED) { + std::cout << "> Error reserving memory! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + exit(1); + } + + if (mprotect(data, size, PROT_READ | PROT_WRITE) != 0) { + std::cout << "> Error allocating memory! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + exit(1); + } + + ++mem_count; + return std::unique_ptr(new mem_info{base, data, alloc_size, fd}); +} + +void free_mem(byte_t*, void* extra) { + std::cout << "> Freeing memory in callback..." << std::endl; + auto info = static_cast(extra); + + close(info->fd); + auto offset = info->data - static_cast(info->base); + if (offset != 0 && munmap(info->base, offset) == -1) { + std::cout << "> Error freeing lo redzone! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + exit(1); + } + + if (munmap(info->data, info->alloc_size) == -1) { + std::cout << "> Error freeing memory! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + exit(1); + } + + delete info; + --mem_count; +} + +auto grow_mem(byte_t* data, void* extra, size_t old_size, size_t new_size) -> byte_t* { + std::cout << "> Growing memory in callback " + << "(old size = " << old_size << ", new size = " << new_size << ")..." + << std::endl; + auto info = static_cast(extra); + + if (ftruncate(info->fd, new_size) == -1) { + close(info->fd); + std::cout << "> Error growing memory file! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + exit(1); + } + + if (mprotect(data + old_size, new_size, PROT_READ | PROT_WRITE) != 0) { + close(info->fd); + std::cout << "> Error resizing memory! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + exit(1); + } + + return data; +} + + +auto get_export_func(const wasm::ownvec& exports, size_t i) -> const wasm::Func* { + if (exports.size() <= i || !exports[i]->func()) { + std::cout << "> Error accessing function export " << i << "!" << std::endl; + exit(1); + } + return exports[i]->func(); +} + +template +void check(T actual, U expected) { + if (actual != expected) { + std::cout << "> Error on result, expected " << expected << ", got " << actual << std::endl; + exit(1); + } +} + +template +void check(byte_t actual, T expected) { + if (actual != expected) { + std::cout << "> Error on result, expected " << expected << ", got " << int(actual) << std::endl; + exit(1); + } +} + +template +void check_ok(const wasm::Func* func, Args... xs) { + wasm::Val args[] = {wasm::Val::i32(xs)...}; + if (func->call(args)) { + std::cout << "> Error on result, expected return" << std::endl; + exit(1); + } +} + +template +void check_trap(const wasm::Func* func, Args... xs) { + wasm::Val args[] = {wasm::Val::i32(xs)...}; + if (! func->call(args)) { + std::cout << "> Error on result, expected trap" << std::endl; + exit(1); + } +} + +template +auto call(const wasm::Func* func, Args... xs) -> int32_t { + wasm::Val args[] = {wasm::Val::i32(xs)...}; + wasm::Val results[1]; + if (func->call(args, results)) { + std::cout << "> Error on result, expected return" << std::endl; + exit(1); + } + return results[0].i32(); +} + + +auto compile(wasm::Engine* engine) -> wasm::own> { + // Load binary. + std::cout << "Loading binary..." << std::endl; + std::ifstream file("mmap.wasm"); + file.seekg(0, std::ios_base::end); + auto file_size = file.tellg(); + file.seekg(0); + auto binary = wasm::vec::make_uninitialized(file_size); + file.read(binary.get(), file_size); + file.close(); + if (file.fail()) { + std::cout << "> Error loading module!" << std::endl; + exit(1); + } + + // Compile. + auto store_ = wasm::Store::make(engine); + auto store = store_.get(); + + std::cout << "Compiling module..." << std::endl; + auto module = wasm::Module::make(store, binary); + if (!module) { + std::cout << "> Error compiling module!" << std::endl; + exit(1); + } + + return module->share(); +} + + +void execute( + wasm::Engine* engine, wasm::Shared* shared_module, int pages, + int run, const std::function&)>& fn +) { + std::cout << "Starting run " << run << "..." << std::endl; + auto store_ = wasm::Store::make(engine); + auto store = store_.get(); + + // Allocate memory. + std::cout << "Allocating memory..." << std::endl; + auto size = pages * wasm::Memory::page_size; + auto info = make_mem(size, open_mem_file(pages)); + + // Create memory. + std::cout << "Creating memory..." << std::endl; + auto memory_type = wasm::MemoryType::make(wasm::Limits(pages)); + + auto memory = wasm::v8::Memory::make_external( + store, memory_type.get(), info->data, info.get(), &grow_mem, &free_mem); + if (!memory) { + std::cout << "> Error creating memory!" << std::endl; + exit(1); + } + info.release(); + + // Instantiate. + std::cout << "Instantiating module..." << std::endl; + auto module = wasm::Module::obtain(store, shared_module); + wasm::Extern* imports[] = {memory.get()}; + auto instance = wasm::Instance::make(store, module.get(), imports); + if (!instance) { + std::cout << "> Error instantiating module!" << std::endl; + exit(1); + } + + // Extract export. + std::cout << "Extracting exports..." << std::endl; + auto exports = instance->exports(); + fn(memory.get(), exports); + + // Done. + std::cout << "Ending run " << run << "..." << std::endl; +} + + +void run() { + // Initialize. + std::cout << "Initializing..." << std::endl; + auto engine = wasm::Engine::make(); + auto shared_module = compile(engine.get()); + + truncate(data_file, 0); // in case it still exists + + // Run 1. + auto pages = 2; + auto run1 = [&]( + wasm::Memory* memory, const wasm::ownvec& exports + ) { + // Extract export. + size_t i = 0; + auto size_func = get_export_func(exports, i++); + auto load_func = get_export_func(exports, i++); + auto store_func = get_export_func(exports, i++); + + // Try cloning. + assert(memory->copy()->same(memory)); + + // Check initial memory. + std::cout << "Checking memory..." << std::endl; + check(memory->size(), 2u); + check(memory->data_size(), 0x20000u); + check(memory->data()[0], 0); + check(memory->data()[0x1000], 0); + check(memory->data()[0x1003], 0); + + check(call(size_func), 2); + check(call(load_func, 0), 0); + check(call(load_func, 0x1000), 0); + check(call(load_func, 0x1003), 0); + check(call(load_func, 0x1ffff), 0); + check_trap(load_func, 0x20000); + + // Mutate memory. + std::cout << "Mutating memory..." << std::endl; + memory->data()[0x1003] = 5; + check_ok(store_func, 0x1002, 6); + check_trap(store_func, 0x20000, 0); + + check(memory->data()[0x1002], 6); + check(memory->data()[0x1003], 5); + check(call(load_func, 0x1002), 6); + check(call(load_func, 0x1003), 5); + + // Grow memory. + std::cout << "Growing memory..." << std::endl; + check(memory->grow(1), true); + check(memory->size(), 3u); + check(memory->data_size(), 0x30000u); + + check(call(load_func, 0x20000), 0); + check_ok(store_func, 0x20000, 0); + check_trap(load_func, 0x30000); + check_trap(store_func, 0x30000, 0); + + check(memory->grow(0), true); + }; + execute(engine.get(), shared_module.get(), pages, 1, run1); + + // Run 2. + pages = 3; + auto run2 = [&]( + wasm::Memory* memory, const wasm::ownvec& exports + ) { + size_t i = 0; + auto size_func = get_export_func(exports, i++); + auto load_func = get_export_func(exports, i++); + auto store_func = get_export_func(exports, i++); + + // Check persisted memory. + std::cout << "Checking memory..." << std::endl; + check(memory->size(), 3u); + check(memory->data_size(), 0x30000u); + check(call(size_func), 3); + + check(memory->data()[0], 0); + check(memory->data()[0x1002], 6); + check(memory->data()[0x1003], 5); + check(call(load_func, 0x1002), 6); + check(call(load_func, 0x1003), 5); + + check_trap(load_func, 0x30000); + check_trap(store_func, 0x30000, 0); + }; + execute(engine.get(), shared_module.get(), pages, 2, run2); + + // Cleaning up. + if (remove(data_file) == -1) { + std::cout << "> Error removing memory file! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + exit(1); + } + + // Shut down. + std::cout << "Shutting down..." << std::endl; +} + + +int main(int argc, const char* argv[]) { + run(); + assert(mem_count == 0); + std::cout << "Done." << std::endl; + return 0; +} + diff --git a/example/mmap.wasm b/example/mmap.wasm new file mode 100644 index 0000000000000000000000000000000000000000..529e5f6470d6de6054bc5fc404008cf260844b63 GIT binary patch literal 128 zcmZQbEY4+QU|?YEZ)j*>U`}9QtWRL92NF#6^$bisAVEe3w%pX*{Gv)G1_tI9urdZl zCidxI7E5tvRVo7mOHO`b3Iii+aY=qrDgzVO60js#F@rq=cRPs7punKRz`)%JWh*dR G0eJuyH6GXi literal 0 HcmV?d00001 diff --git a/example/mmap.wat b/example/mmap.wat new file mode 100644 index 0000000..f893e33 --- /dev/null +++ b/example/mmap.wat @@ -0,0 +1,9 @@ +(module + (memory (import "" "memory") 0) + + (func (export "size") (result i32) (memory.size)) + (func (export "load") (param i32) (result i32) (i32.load8_s (local.get 0))) + (func (export "store") (param i32 i32) + (i32.store8 (local.get 0) (local.get 1)) + ) +) diff --git a/include/wasm-v8.hh b/include/wasm-v8.hh new file mode 100644 index 0000000..5ba9b14 --- /dev/null +++ b/include/wasm-v8.hh @@ -0,0 +1,31 @@ +// V8 vendor-specific extensions to WebAssembly C++ API + +#ifndef __WASM_V8_HH +#define __WASM_V8_HH + +#include "wasm.hh" + + +namespace wasm { +namespace v8 { + +/////////////////////////////////////////////////////////////////////////////// + +namespace Memory { + using grow_callback_t = auto (*)(byte_t*, void*, size_t, size_t) -> byte_t*; + using free_callback_t = void (*)(byte_t*, void*); + + auto make_external( + Store*, const MemoryType*, byte_t*, void*, grow_callback_t, free_callback_t + ) -> own; + + auto redzone_size_lo(size_t) -> size_t; + auto redzone_size_hi(size_t) -> size_t; +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace v8 +} // namespace wasm + +#endif // #ifdef __WASM_V8_HH diff --git a/patch/0002-wasm-objects-add-host-owned-memory.patch b/patch/0002-wasm-objects-add-host-owned-memory.patch new file mode 100644 index 0000000..7a1ed1c --- /dev/null +++ b/patch/0002-wasm-objects-add-host-owned-memory.patch @@ -0,0 +1,136 @@ +diff --git a/src/wasm/wasm-objects-inl.h b/src/wasm/wasm-objects-inl.h +index e1fc2d2410..ec3f04be57 100644 +--- a/src/wasm/wasm-objects-inl.h ++++ b/src/wasm/wasm-objects-inl.h +@@ -125,6 +125,7 @@ SMI_ACCESSORS(WasmTableObject, raw_type, kRawTypeOffset) + // WasmMemoryObject + ACCESSORS(WasmMemoryObject, array_buffer, JSArrayBuffer, kArrayBufferOffset) + SMI_ACCESSORS(WasmMemoryObject, maximum_pages, kMaximumPagesOffset) ++ACCESSORS(WasmMemoryObject, host_owned, Object, kHostOwnedOffset) + OPTIONAL_ACCESSORS(WasmMemoryObject, instances, WeakArrayList, kInstancesOffset) + + // WasmGlobalObject +diff --git a/src/wasm/wasm-objects.cc b/src/wasm/wasm-objects.cc +index 27a56695c2..55d205b534 100644 +--- a/src/wasm/wasm-objects.cc ++++ b/src/wasm/wasm-objects.cc +@@ -1223,9 +1223,23 @@ void SetInstanceMemory(Handle instance, + + } // namespace + ++WasmMemoryObject::HostOwnedStoreInfo::~HostOwnedStoreInfo() { ++ DCHECK(memory); ++ auto buffer = memory->array_buffer(); ++ DCHECK(buffer.IsJSArrayBuffer()); ++ void* store = buffer.backing_store(); ++ ++/*TODO ++ Isolate* isolate = buffer.GetIsolate(); ++ HandleScope(isolate) handle_scope; ++ wasm::DetachMemoryBuffer(isolate, Handle(buffer), false); ++*/ ++ if (free_callback) free_callback(store, callback_info); ++} ++ + Handle WasmMemoryObject::New( + Isolate* isolate, MaybeHandle maybe_buffer, +- uint32_t maximum) { ++ uint32_t maximum, std::unique_ptr host_owned) { + Handle buffer; + if (!maybe_buffer.ToHandle(&buffer)) { + // If no buffer was provided, create a 0-length one. +@@ -1243,6 +1257,20 @@ Handle WasmMemoryObject::New( + isolate->factory()->NewJSObject(memory_ctor, AllocationType::kOld)); + memory_obj->set_array_buffer(*buffer); + memory_obj->set_maximum_pages(maximum); ++ if (host_owned) { ++ DCHECK(host_owned->memory.is_null()); ++ host_owned->memory = ++ isolate->global_handles()->Create(*memory_obj); ++/*TODO ++ GlobalHandles::MakeWeak( ++ reinterpret_cast(host_owned->memory.address())); ++*/ ++ auto managed = Managed::FromUniquePtr( ++ isolate, sizeof(HostOwnedStoreInfo), std::move(host_owned)); ++ memory_obj->set_host_owned(*managed); ++ } else { ++ memory_obj->set_host_owned(ReadOnlyRoots(isolate).undefined_value()); ++ } + + return memory_obj; + } +@@ -1308,10 +1336,16 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, + Handle memory_object, + uint32_t pages) { + TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "GrowMemory"); ++ HostOwnedStoreInfo* host_owned = nullptr; ++ if (!memory_object->IsUndefined()) { ++ host_owned = ++ Managed::cast(memory_object->host_owned())->raw(); ++ } + Handle old_buffer(memory_object->array_buffer(), isolate); + if (old_buffer->is_shared() && !FLAG_wasm_grow_shared_memory) return -1; + auto* memory_tracker = isolate->wasm_engine()->memory_tracker(); +- if (!memory_tracker->IsWasmMemoryGrowable(old_buffer)) return -1; ++ if (!(host_owned || memory_tracker->IsWasmMemoryGrowable(old_buffer))) ++ return -1; + + // Checks for maximum memory size, compute new size. + uint32_t maximum_pages = wasm::max_mem_pages(); +@@ -1335,7 +1369,19 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, + // Handle this in the interrupt handler so that it's safe for all the isolates + // that share this buffer to be updated safely. + Handle new_buffer; +- if (old_buffer->is_shared()) { ++ if (host_owned) { ++ // TODO: handle shared host-owned memory once we support it. ++ auto new_store = host_owned->grow_callback( ++ old_buffer->backing_store(), host_owned->callback_info, ++ old_size, new_size); ++ if (!new_store) return -1; ++ bool is_external = old_buffer->is_external(); ++ DCHECK(is_external); ++ // Disconnect buffer early so GC won't free it. ++ wasm::DetachMemoryBuffer(isolate, old_buffer, false); ++ new_buffer = ++ wasm::SetupArrayBuffer(isolate, new_store, new_size, is_external); ++ } else if (old_buffer->is_shared()) { + // Adjust protections for the buffer. + if (!AdjustBufferPermissions(isolate, old_buffer, new_size)) { + return -1; +diff --git a/src/wasm/wasm-objects.h b/src/wasm/wasm-objects.h +index 1e6ced0b76..434514f947 100644 +--- a/src/wasm/wasm-objects.h ++++ b/src/wasm/wasm-objects.h +@@ -327,10 +327,20 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject { + // Representation of a WebAssembly.Memory JavaScript-level object. + class WasmMemoryObject : public JSObject { + public: ++ struct HostOwnedStoreInfo { ++ auto (*grow_callback)(void*, void*, size_t, size_t) -> void* = nullptr; ++ void (*free_callback)(void*, void*) = nullptr; ++ void* callback_info = nullptr; ++ Handle memory = Handle(); ++ ++ ~HostOwnedStoreInfo(); ++ }; ++ + DECL_CAST(WasmMemoryObject) + + DECL_ACCESSORS(array_buffer, JSArrayBuffer) + DECL_INT_ACCESSORS(maximum_pages) ++ DECL_ACCESSORS(host_owned, Object) + DECL_OPTIONAL_ACCESSORS(instances, WeakArrayList) + + // Dispatched behavior. +@@ -347,7 +357,8 @@ class WasmMemoryObject : public JSObject { + inline bool has_maximum_pages(); + + V8_EXPORT_PRIVATE static Handle New( +- Isolate* isolate, MaybeHandle buffer, uint32_t maximum); ++ Isolate* isolate, MaybeHandle buffer, uint32_t maximum, ++ std::unique_ptr host_owned = nullptr); + + V8_EXPORT_PRIVATE static MaybeHandle New( + Isolate* isolate, uint32_t initial, uint32_t maximum, diff --git a/src/wasm-v8-lowlevel.cc b/src/wasm-v8-lowlevel.cc index 065f78e..3af97e1 100644 --- a/src/wasm-v8-lowlevel.cc +++ b/src/wasm-v8-lowlevel.cc @@ -10,6 +10,7 @@ #include "objects/ordered-hash-table.h" #include "objects/js-promise.h" #include "objects/js-collection.h" +#include "objects/js-array-buffer.h" #include "api/api.h" #include "api/api-inl.h" @@ -371,6 +372,25 @@ auto table_grow( // Memory +auto memory_new_external( + v8::Isolate* isolate, void* data, uint32_t min, uint32_t max, + void* info, memory_grow_callback_t grow, memory_free_callback_t free +) -> v8::MaybeLocal { + auto i_isolate = reinterpret_cast(isolate); + auto size = + static_cast(min) * + static_cast(v8::internal::wasm::kWasmPageSize); + auto buffer = v8::internal::wasm::SetupArrayBuffer( + i_isolate, data, size, true, v8::internal::SharedFlag::kNotShared); + auto desc = + new(std::nothrow) v8::internal::WasmMemoryObject::HostOwnedStoreInfo{ + grow, free, info}; + if (!desc) return v8::MaybeLocal(); + auto memory = v8::internal::WasmMemoryObject::New(i_isolate, buffer, max, + std::unique_ptr(desc)); + return v8::Utils::ToLocal(v8::internal::Handle::cast(memory)); +} + auto memory_data(v8::Local memory) -> char* { auto v8_object = v8::Utils::OpenHandle(memory); auto v8_memory = v8::internal::Handle::cast(v8_object); @@ -398,5 +418,13 @@ auto memory_grow(v8::Local memory, uint32_t delta) -> bool { return old != -1; } +auto memory_redzone_size_lo(size_t) -> size_t { + return V8_TARGET_ARCH_64_BIT ? 1u << 31 : 0; +} + +auto memory_redzone_size_hi(size_t size) -> size_t { + return V8_TARGET_ARCH_64_BIT ? v8::internal::wasm::kWasmMaxHeapOffset - size : 0; +} + } // namespace wasm } // namespace v8 diff --git a/src/wasm-v8-lowlevel.hh b/src/wasm-v8-lowlevel.hh index 6737d07..f94689a 100644 --- a/src/wasm-v8-lowlevel.hh +++ b/src/wasm-v8-lowlevel.hh @@ -63,10 +63,19 @@ auto table_set(v8::Local table, size_t index, v8::Local) auto table_size(v8::Local table) -> size_t; auto table_grow(v8::Local table, size_t delta, v8::Local) -> bool; +using memory_grow_callback_t = auto (*)(void*, void*, size_t, size_t) -> void*; +using memory_free_callback_t = void (*)(void*, void*); +auto memory_new_external( + v8::Isolate*, void*, uint32_t min, uint32_t max, + void*, memory_grow_callback_t, memory_free_callback_t +) -> v8::MaybeLocal; + auto memory_data(v8::Local memory) -> char*; auto memory_data_size(v8::Local memory)-> size_t; auto memory_size(v8::Local memory) -> uint32_t; auto memory_grow(v8::Local memory, uint32_t delta) -> bool; +auto memory_redzone_size_lo(size_t) -> size_t; +auto memory_redzone_size_hi(size_t) -> size_t; } // namespace wasm } // namespace v8 diff --git a/src/wasm-v8.cc b/src/wasm-v8.cc index b2bb32c..32d5f5c 100644 --- a/src/wasm-v8.cc +++ b/src/wasm-v8.cc @@ -2185,3 +2185,51 @@ auto Instance::exports() const -> ownvec { /////////////////////////////////////////////////////////////////////////////// } // namespace wasm + + + +/////////////////////////////////////////////////////////////////////////////// +// Vendor-specific extensions + +#include "wasm-v8.hh" + +namespace wasm { +namespace v8 { + +namespace Memory { + auto make_external( + Store* store_abs, const MemoryType* type, byte_t* mem, + void* info, grow_callback_t grow, free_callback_t free + ) -> own { + auto store = impl(store_abs); + auto isolate = store->isolate(); + ::v8::HandleScope handle_scope(isolate); + + auto min = type->limits().min; + auto max = type->limits().max; + assert(min <= max); + assert(max < 0x10000u || max == std::numeric_limits::max()); + assert(min == 0 || mem[0] == mem[0]); + assert(min == 0 || mem[min-1] == mem[min-1]); + + // TODO: handle grow and free + auto maybe_obj = wasm_v8::memory_new_external( + isolate, mem, min, max, info, + reinterpret_cast(grow), + reinterpret_cast(free)); + if (maybe_obj.IsEmpty()) return nullptr; + return RefImpl::make(store, maybe_obj.ToLocalChecked()); + } + + auto redzone_size_lo(size_t size) -> size_t { + return wasm_v8::memory_redzone_size_lo(size); + } + auto redzone_size_hi(size_t size) -> size_t { + return wasm_v8::memory_redzone_size_hi(size); + } +} + +} // namespace v8 +} // namespace wasm + +/////////////////////////////////////////////////////////////////////////////// From d0647a8256b715ddcc8bb755e049c4bd334adfba Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 26 Jul 2019 16:35:38 +0200 Subject: [PATCH 02/11] Add internal grow to example --- example/mmap.cc | 37 ++++++++++++++++++++++++++++--------- example/mmap.wasm | Bin 128 -> 147 bytes example/mmap.wat | 1 + 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/example/mmap.cc b/example/mmap.cc index 4840c34..9034c61 100644 --- a/example/mmap.cc +++ b/example/mmap.cc @@ -274,6 +274,7 @@ void run() { auto size_func = get_export_func(exports, i++); auto load_func = get_export_func(exports, i++); auto store_func = get_export_func(exports, i++); + auto grow_func = get_export_func(exports, i++); // Try cloning. assert(memory->copy()->same(memory)); @@ -310,17 +311,31 @@ void run() { check(memory->size(), 3u); check(memory->data_size(), 0x30000u); - check(call(load_func, 0x20000), 0); - check_ok(store_func, 0x20000, 0); + check_ok(store_func, 0x20000, 7); + memory->data()[0x20001] = 8; + check(call(load_func, 0x20000), 7); + check(call(load_func, 0x20001), 8); + check_trap(load_func, 0x30000); check_trap(store_func, 0x30000, 0); check(memory->grow(0), true); + + check(call(grow_func, 2), 3); + check(memory->size(), 5u); + + check_ok(store_func, 0x40000, 10); + memory->data()[0x40001] = 11; + check(call(load_func, 0x40000), 10); + check(call(load_func, 0x40001), 11); + + check_trap(load_func, 0x50000); + check_trap(store_func, 0x50000, 0); }; execute(engine.get(), shared_module.get(), pages, 1, run1); // Run 2. - pages = 3; + pages = 5; auto run2 = [&]( wasm::Memory* memory, const wasm::ownvec& exports ) { @@ -331,20 +346,24 @@ void run() { // Check persisted memory. std::cout << "Checking memory..." << std::endl; - check(memory->size(), 3u); - check(memory->data_size(), 0x30000u); - check(call(size_func), 3); + check(memory->size(), 5u); + check(memory->data_size(), 0x50000u); + check(call(size_func), 5); check(memory->data()[0], 0); check(memory->data()[0x1002], 6); check(memory->data()[0x1003], 5); check(call(load_func, 0x1002), 6); check(call(load_func, 0x1003), 5); + check(call(load_func, 0x20000), 7); + check(call(load_func, 0x20001), 8); + check(call(load_func, 0x40000), 10); + check(call(load_func, 0x40001), 11); - check_trap(load_func, 0x30000); - check_trap(store_func, 0x30000, 0); + check_trap(load_func, 0x50000); + check_trap(store_func, 0x50000, 0); }; - execute(engine.get(), shared_module.get(), pages, 2, run2); + execute(engine.get(), shared_module.get(), pages, 1, run2); // Cleaning up. if (remove(data_file) == -1) { diff --git a/example/mmap.wasm b/example/mmap.wasm index 529e5f6470d6de6054bc5fc404008cf260844b63..997e452f2c7a38ceaeaf01000195113fd0c8f440 100644 GIT binary patch delta 57 zcmZo*oXj}EK(Mu;p@D&gfsu)keIA%O(Ls!lCA}!WoPn8ZJwzl{nY#_dVo+dkVBiJ- Dy0H$T delta 37 lcmbQt*uXf!K%k|ep@D&!fsu)QI+!)lQH-5y351!d3;@F*3Q7O~ diff --git a/example/mmap.wat b/example/mmap.wat index f893e33..cc7904e 100644 --- a/example/mmap.wat +++ b/example/mmap.wat @@ -6,4 +6,5 @@ (func (export "store") (param i32 i32) (i32.store8 (local.get 0) (local.get 1)) ) + (func (export "grow") (param i32) (result i32) (memory.grow (local.get 0))) ) From d89a6b7df7f9242806f4bab8fba73c40dbe95c75 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 26 Jul 2019 16:37:24 +0200 Subject: [PATCH 03/11] Example eps --- example/mmap.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/example/mmap.cc b/example/mmap.cc index 9034c61..11f4fd4 100644 --- a/example/mmap.cc +++ b/example/mmap.cc @@ -360,6 +360,11 @@ void run() { check(call(load_func, 0x40000), 10); check(call(load_func, 0x40001), 11); + check_ok(store_func, 0x40002, 12); + memory->data()[0x40003] = 13; + check(call(load_func, 0x40002), 12); + check(call(load_func, 0x40003), 13); + check_trap(load_func, 0x50000); check_trap(store_func, 0x50000, 0); }; From ba419f908a15c418031e22e779edb5cabba7fa3f Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Fri, 26 Jul 2019 22:17:52 +0200 Subject: [PATCH 04/11] Tweak API --- example/mmap.cc | 9 +-- include/wasm-v8.hh | 4 +- ...2-wasm-objects-add-host-owned-memory.patch | 55 +++++++++---------- src/wasm-v8-lowlevel.hh | 2 +- 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/example/mmap.cc b/example/mmap.cc index 11f4fd4..33de27b 100644 --- a/example/mmap.cc +++ b/example/mmap.cc @@ -80,8 +80,9 @@ auto make_mem(size_t size, int fd) -> std::unique_ptr { return std::unique_ptr(new mem_info{base, data, alloc_size, fd}); } -void free_mem(byte_t*, void* extra) { - std::cout << "> Freeing memory in callback..." << std::endl; +void free_mem(void* extra, byte_t* data, size_t size) { + std::cout << "> Freeing memory in callback " + << "(size = " << size << ")..." << std::endl; auto info = static_cast(extra); close(info->fd); @@ -102,7 +103,7 @@ void free_mem(byte_t*, void* extra) { --mem_count; } -auto grow_mem(byte_t* data, void* extra, size_t old_size, size_t new_size) -> byte_t* { +auto grow_mem(void* extra, byte_t* data, size_t old_size, size_t new_size) -> byte_t* { std::cout << "> Growing memory in callback " << "(old size = " << old_size << ", new size = " << new_size << ")..." << std::endl; @@ -368,7 +369,7 @@ void run() { check_trap(load_func, 0x50000); check_trap(store_func, 0x50000, 0); }; - execute(engine.get(), shared_module.get(), pages, 1, run2); + execute(engine.get(), shared_module.get(), pages, 2, run2); // Cleaning up. if (remove(data_file) == -1) { diff --git a/include/wasm-v8.hh b/include/wasm-v8.hh index 5ba9b14..a70b34d 100644 --- a/include/wasm-v8.hh +++ b/include/wasm-v8.hh @@ -12,8 +12,8 @@ namespace v8 { /////////////////////////////////////////////////////////////////////////////// namespace Memory { - using grow_callback_t = auto (*)(byte_t*, void*, size_t, size_t) -> byte_t*; - using free_callback_t = void (*)(byte_t*, void*); + using grow_callback_t = auto (*)(void*, byte_t*, size_t, size_t) -> byte_t*; + using free_callback_t = void (*)(void*, byte_t*, size_t); auto make_external( Store*, const MemoryType*, byte_t*, void*, grow_callback_t, free_callback_t diff --git a/patch/0002-wasm-objects-add-host-owned-memory.patch b/patch/0002-wasm-objects-add-host-owned-memory.patch index 7a1ed1c..15318b9 100644 --- a/patch/0002-wasm-objects-add-host-owned-memory.patch +++ b/patch/0002-wasm-objects-add-host-owned-memory.patch @@ -11,25 +11,21 @@ index e1fc2d2410..ec3f04be57 100644 // WasmGlobalObject diff --git a/src/wasm/wasm-objects.cc b/src/wasm/wasm-objects.cc -index 27a56695c2..55d205b534 100644 +index 27a56695c2..2a807e600c 100644 --- a/src/wasm/wasm-objects.cc +++ b/src/wasm/wasm-objects.cc -@@ -1223,9 +1223,23 @@ void SetInstanceMemory(Handle instance, +@@ -1223,9 +1223,19 @@ void SetInstanceMemory(Handle instance, } // namespace +WasmMemoryObject::HostOwnedStoreInfo::~HostOwnedStoreInfo() { -+ DCHECK(memory); -+ auto buffer = memory->array_buffer(); -+ DCHECK(buffer.IsJSArrayBuffer()); -+ void* store = buffer.backing_store(); -+ -+/*TODO -+ Isolate* isolate = buffer.GetIsolate(); -+ HandleScope(isolate) handle_scope; -+ wasm::DetachMemoryBuffer(isolate, Handle(buffer), false); -+*/ -+ if (free_callback) free_callback(store, callback_info); ++ // TODO: This is a hack. If the associated ArrayBuffer is still alive, ++ // it will have a dangling pointer as packing store at this point. ++ // (However, that should only matter when mixing Wasm and V8 API.) ++ // To fix, the life time of the backing store should be tied to the buffer. ++ // However, that would require attaching this structue to the buffer ++ // instead of the memory object. Possible with some extra flag bit there? ++ if (free_callback) free_callback(callback_info, data, size); +} + Handle WasmMemoryObject::New( @@ -39,28 +35,25 @@ index 27a56695c2..55d205b534 100644 Handle buffer; if (!maybe_buffer.ToHandle(&buffer)) { // If no buffer was provided, create a 0-length one. -@@ -1243,6 +1257,20 @@ Handle WasmMemoryObject::New( +@@ -1243,6 +1253,17 @@ Handle WasmMemoryObject::New( isolate->factory()->NewJSObject(memory_ctor, AllocationType::kOld)); memory_obj->set_array_buffer(*buffer); memory_obj->set_maximum_pages(maximum); + if (host_owned) { + DCHECK(host_owned->memory.is_null()); -+ host_owned->memory = -+ isolate->global_handles()->Create(*memory_obj); -+/*TODO -+ GlobalHandles::MakeWeak( -+ reinterpret_cast(host_owned->memory.address())); -+*/ ++ host_owned->data = buffer->backing_store(); ++ host_owned->size = buffer->byte_length(); + auto managed = Managed::FromUniquePtr( + isolate, sizeof(HostOwnedStoreInfo), std::move(host_owned)); + memory_obj->set_host_owned(*managed); ++ buffer->set_is_wasm_memory(false); + } else { + memory_obj->set_host_owned(ReadOnlyRoots(isolate).undefined_value()); + } return memory_obj; } -@@ -1308,10 +1336,16 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, +@@ -1308,10 +1329,16 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, Handle memory_object, uint32_t pages) { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "GrowMemory"); @@ -78,16 +71,15 @@ index 27a56695c2..55d205b534 100644 // Checks for maximum memory size, compute new size. uint32_t maximum_pages = wasm::max_mem_pages(); -@@ -1335,7 +1369,19 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, +@@ -1335,7 +1362,20 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, // Handle this in the interrupt handler so that it's safe for all the isolates // that share this buffer to be updated safely. Handle new_buffer; - if (old_buffer->is_shared()) { + if (host_owned) { + // TODO: handle shared host-owned memory once we support it. -+ auto new_store = host_owned->grow_callback( -+ old_buffer->backing_store(), host_owned->callback_info, -+ old_size, new_size); ++ auto new_store = host_owned->grow_callback(host_owned->callback_info, ++ old_buffer->backing_store(), old_size, new_size); + if (!new_store) return -1; + bool is_external = old_buffer->is_external(); + DCHECK(is_external); @@ -95,23 +87,26 @@ index 27a56695c2..55d205b534 100644 + wasm::DetachMemoryBuffer(isolate, old_buffer, false); + new_buffer = + wasm::SetupArrayBuffer(isolate, new_store, new_size, is_external); ++ host_owned->data = new_store; ++ host_owned->size = new_size; + } else if (old_buffer->is_shared()) { // Adjust protections for the buffer. if (!AdjustBufferPermissions(isolate, old_buffer, new_size)) { return -1; diff --git a/src/wasm/wasm-objects.h b/src/wasm/wasm-objects.h -index 1e6ced0b76..434514f947 100644 +index 1e6ced0b76..c3ecf4efe0 100644 --- a/src/wasm/wasm-objects.h +++ b/src/wasm/wasm-objects.h -@@ -327,10 +327,20 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject { +@@ -327,10 +327,21 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject { // Representation of a WebAssembly.Memory JavaScript-level object. class WasmMemoryObject : public JSObject { public: + struct HostOwnedStoreInfo { + auto (*grow_callback)(void*, void*, size_t, size_t) -> void* = nullptr; -+ void (*free_callback)(void*, void*) = nullptr; ++ void (*free_callback)(void*, void*, size_t) = nullptr; + void* callback_info = nullptr; -+ Handle memory = Handle(); ++ void* data = nullptr; ++ size_t size = 0; + + ~HostOwnedStoreInfo(); + }; @@ -124,7 +119,7 @@ index 1e6ced0b76..434514f947 100644 DECL_OPTIONAL_ACCESSORS(instances, WeakArrayList) // Dispatched behavior. -@@ -347,7 +357,8 @@ class WasmMemoryObject : public JSObject { +@@ -347,7 +358,8 @@ class WasmMemoryObject : public JSObject { inline bool has_maximum_pages(); V8_EXPORT_PRIVATE static Handle New( diff --git a/src/wasm-v8-lowlevel.hh b/src/wasm-v8-lowlevel.hh index f94689a..0360dbc 100644 --- a/src/wasm-v8-lowlevel.hh +++ b/src/wasm-v8-lowlevel.hh @@ -64,7 +64,7 @@ auto table_size(v8::Local table) -> size_t; auto table_grow(v8::Local table, size_t delta, v8::Local) -> bool; using memory_grow_callback_t = auto (*)(void*, void*, size_t, size_t) -> void*; -using memory_free_callback_t = void (*)(void*, void*); +using memory_free_callback_t = void (*)(void*, void*, size_t); auto memory_new_external( v8::Isolate*, void*, uint32_t min, uint32_t max, void*, memory_grow_callback_t, memory_free_callback_t From 1a77d18e879128a0dbaf6ad845d14677cfc36e2b Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Sat, 27 Jul 2019 09:41:07 +0200 Subject: [PATCH 05/11] Doc; tweaks --- Makefile | 11 +++++--- example/mmap.cc | 11 +++++++- include/wasm-v8.hh | 26 ++++++++++++++++++- ...2-wasm-objects-add-host-owned-memory.patch | 5 ++-- src/wasm-v8.cc | 4 +-- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 678b233..244468d 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ OUT_DIR = out EXAMPLE_OUT = ${OUT_DIR}/${EXAMPLE_DIR} EXAMPLES = \ mmap -BLA = \ +TEMPORARILY_DISABLED_DO_NOT_COMMIT = \ hello \ callback \ trap \ @@ -225,15 +225,18 @@ v8-build: .PHONY: v8-patch v8-patch: if ! grep ${WASM_V8_PATCH} ${V8_V8}/BUILD.gn; then \ - cp ${V8_V8}/BUILD.gn ${V8_V8}/BUILD.gn.save; \ cd ${V8_V8}; \ - patch < ../../patch/0001-BUILD.gn-add-wasm-v8-lowlevel.patch; \ + cp BUILD.gn BUILD.gn.save; \ + patch -p1 <../../patch/0001-BUILD.gn-add-wasm-v8-lowlevel.patch; \ + patch -p1 <../../patch/0002-wasm-objects-add-host-owned-memory.patch; \ fi .PHONY: v8-unpatch v8-unpatch: if [ -f ${V8_V8}/BUILD.gn.save ]; then \ - mv -f ${V8_V8}/BUILD.gn.save ${V8_V8}/BUILD.gn; \ + cd ${V8_V8}; \ + patch -p1 -R <../../patch/0001-BUILD.gn-add-wasm-v8-lowlevel.patch; \ + patch -p1 -R <../../patch/0002-wasm-objects-add-host-owned-memory.patch; \ fi ${V8_INCLUDE}/${WASM_V8_PATCH}.hh: ${WASM_SRC}/${WASM_V8_PATCH}.hh diff --git a/example/mmap.cc b/example/mmap.cc index 33de27b..204e9c6 100644 --- a/example/mmap.cc +++ b/example/mmap.cc @@ -53,6 +53,7 @@ auto make_mem(size_t size, int fd) -> std::unique_ptr { void* base = nullptr; if (offset > 0) { + // Lo redzone needed. base = mmap(nullptr, offset, PROT_NONE, MAP_ANON | MAP_PRIVATE, 0, 0); if (base == MAP_FAILED) { std::cout << "> Error reserving lo redzone! errno = " << errno @@ -109,6 +110,14 @@ auto grow_mem(void* extra, byte_t* data, size_t old_size, size_t new_size) -> by << std::endl; auto info = static_cast(extra); + // Only support in-place growth. + if (wasm::v8::Memory::redzone_size_lo(new_size) > + wasm::v8::Memory::redzone_size_lo(old_size) || + new_size + wasm::v8::Memory::redzone_size_hi(new_size) > + old_size + wasm::v8::Memory::redzone_size_hi(old_size)) { + return nullptr; + } + if (ftruncate(info->fd, new_size) == -1) { close(info->fd); std::cout << "> Error growing memory file! errno = " << errno @@ -230,7 +239,7 @@ void execute( auto memory_type = wasm::MemoryType::make(wasm::Limits(pages)); auto memory = wasm::v8::Memory::make_external( - store, memory_type.get(), info->data, info.get(), &grow_mem, &free_mem); + store, memory_type.get(), info->data, grow_mem, free_mem, info.release()); if (!memory) { std::cout << "> Error creating memory!" << std::endl; exit(1); diff --git a/include/wasm-v8.hh b/include/wasm-v8.hh index a70b34d..7000bdd 100644 --- a/include/wasm-v8.hh +++ b/include/wasm-v8.hh @@ -15,8 +15,32 @@ namespace Memory { using grow_callback_t = auto (*)(void*, byte_t*, size_t, size_t) -> byte_t*; using free_callback_t = void (*)(void*, byte_t*, size_t); + // Create a Memory backed by an external storage. + // For a memory type with limits.min = S, It is the callers responsibility to + // * provide a readable and writable, zeroed byte array of size S + // * install an inaccessible redzone address range of size redzone_size_lo(S) + // right before the byte vector's address range + // * install an inaccessible redzone address range of size redzone_size_hi(S) + // right after the byte vector's address range + // * optionally, provide a `grow_callback` that is invoked by the engine when + // the Memory needs to grow; it receives the current byte vector, its + // current size, and the new size requested; it needs to return the address + // of a new byte vector with redzones installed as before, or `nullptr`to + // reject the request; the new byte vector can be the same as the old if the + // host is able to grow it in place; if not, it is the host's responsibility + // to copy the contents from the old to the new vector; the additional + // bytes must be zeroed; if no `grow_callback` is provided, all grow + // requests for the Memory will be rejected + // * optionally, provide a `free_callback` that is invoked by the engine when + // the Memory is no longer needed; it receives the current byte vector and + // the current size; when invoked, the host can free the bytee vector and + // associated redzones + // * optionally, provide an additional parameter that is stored by the engine + // and passed on to the callbacks as their first argument; the host should + // free any associated allocation in thee `free_callback` auto make_external( - Store*, const MemoryType*, byte_t*, void*, grow_callback_t, free_callback_t + Store*, const MemoryType*, byte_t*, + grow_callback_t = nullptr, free_callback_t = nullptr, void* = nullptr ) -> own; auto redzone_size_lo(size_t) -> size_t; diff --git a/patch/0002-wasm-objects-add-host-owned-memory.patch b/patch/0002-wasm-objects-add-host-owned-memory.patch index 15318b9..720ff9b 100644 --- a/patch/0002-wasm-objects-add-host-owned-memory.patch +++ b/patch/0002-wasm-objects-add-host-owned-memory.patch @@ -11,7 +11,7 @@ index e1fc2d2410..ec3f04be57 100644 // WasmGlobalObject diff --git a/src/wasm/wasm-objects.cc b/src/wasm/wasm-objects.cc -index 27a56695c2..2a807e600c 100644 +index 27a56695c2..fb15ca22ba 100644 --- a/src/wasm/wasm-objects.cc +++ b/src/wasm/wasm-objects.cc @@ -1223,9 +1223,19 @@ void SetInstanceMemory(Handle instance, @@ -71,13 +71,14 @@ index 27a56695c2..2a807e600c 100644 // Checks for maximum memory size, compute new size. uint32_t maximum_pages = wasm::max_mem_pages(); -@@ -1335,7 +1362,20 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, +@@ -1335,7 +1362,21 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, // Handle this in the interrupt handler so that it's safe for all the isolates // that share this buffer to be updated safely. Handle new_buffer; - if (old_buffer->is_shared()) { + if (host_owned) { + // TODO: handle shared host-owned memory once we support it. ++ if (!host_owned->grow_callback) return -1; + auto new_store = host_owned->grow_callback(host_owned->callback_info, + old_buffer->backing_store(), old_size, new_size); + if (!new_store) return -1; diff --git a/src/wasm-v8.cc b/src/wasm-v8.cc index 32d5f5c..daf1b85 100644 --- a/src/wasm-v8.cc +++ b/src/wasm-v8.cc @@ -2199,7 +2199,7 @@ namespace v8 { namespace Memory { auto make_external( Store* store_abs, const MemoryType* type, byte_t* mem, - void* info, grow_callback_t grow, free_callback_t free + grow_callback_t grow, free_callback_t free, void* info ) -> own { auto store = impl(store_abs); auto isolate = store->isolate(); @@ -2209,8 +2209,6 @@ namespace Memory { auto max = type->limits().max; assert(min <= max); assert(max < 0x10000u || max == std::numeric_limits::max()); - assert(min == 0 || mem[0] == mem[0]); - assert(min == 0 || mem[min-1] == mem[min-1]); // TODO: handle grow and free auto maybe_obj = wasm_v8::memory_new_external( From 2ff50c67193320f97e65e10a7a50e31429855c76 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Sat, 27 Jul 2019 14:51:08 +0200 Subject: [PATCH 06/11] Tweaks --- include/wasm-v8.hh | 5 ++-- ...2-wasm-objects-add-host-owned-memory.patch | 26 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/include/wasm-v8.hh b/include/wasm-v8.hh index 7000bdd..ff2ea32 100644 --- a/include/wasm-v8.hh +++ b/include/wasm-v8.hh @@ -24,13 +24,14 @@ namespace Memory { // right after the byte vector's address range // * optionally, provide a `grow_callback` that is invoked by the engine when // the Memory needs to grow; it receives the current byte vector, its - // current size, and the new size requested; it needs to return the address + // current size, and the new size requested (it is an invariant that + // new size > old size when invoked); it needs to return the address // of a new byte vector with redzones installed as before, or `nullptr`to // reject the request; the new byte vector can be the same as the old if the // host is able to grow it in place; if not, it is the host's responsibility // to copy the contents from the old to the new vector; the additional // bytes must be zeroed; if no `grow_callback` is provided, all grow - // requests for the Memory will be rejected + // requests for the Memory will be rejected except if the delta is zero // * optionally, provide a `free_callback` that is invoked by the engine when // the Memory is no longer needed; it receives the current byte vector and // the current size; when invoked, the host can free the bytee vector and diff --git a/patch/0002-wasm-objects-add-host-owned-memory.patch b/patch/0002-wasm-objects-add-host-owned-memory.patch index 720ff9b..413f87b 100644 --- a/patch/0002-wasm-objects-add-host-owned-memory.patch +++ b/patch/0002-wasm-objects-add-host-owned-memory.patch @@ -11,7 +11,7 @@ index e1fc2d2410..ec3f04be57 100644 // WasmGlobalObject diff --git a/src/wasm/wasm-objects.cc b/src/wasm/wasm-objects.cc -index 27a56695c2..fb15ca22ba 100644 +index 27a56695c2..f24d752e34 100644 --- a/src/wasm/wasm-objects.cc +++ b/src/wasm/wasm-objects.cc @@ -1223,9 +1223,19 @@ void SetInstanceMemory(Handle instance, @@ -53,17 +53,12 @@ index 27a56695c2..fb15ca22ba 100644 return memory_obj; } -@@ -1308,10 +1329,16 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, - Handle memory_object, - uint32_t pages) { +@@ -1310,8 +1331,11 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "GrowMemory"); -+ HostOwnedStoreInfo* host_owned = nullptr; -+ if (!memory_object->IsUndefined()) { -+ host_owned = -+ Managed::cast(memory_object->host_owned())->raw(); -+ } Handle old_buffer(memory_object->array_buffer(), isolate); if (old_buffer->is_shared() && !FLAG_wasm_grow_shared_memory) return -1; ++ HostOwnedStoreInfo* host_owned = ++ Managed::cast(memory_object->host_owned())->raw(); auto* memory_tracker = isolate->wasm_engine()->memory_tracker(); - if (!memory_tracker->IsWasmMemoryGrowable(old_buffer)) return -1; + if (!(host_owned || memory_tracker->IsWasmMemoryGrowable(old_buffer))) @@ -71,17 +66,20 @@ index 27a56695c2..fb15ca22ba 100644 // Checks for maximum memory size, compute new size. uint32_t maximum_pages = wasm::max_mem_pages(); -@@ -1335,7 +1362,21 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, +@@ -1335,7 +1359,24 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, // Handle this in the interrupt handler so that it's safe for all the isolates // that share this buffer to be updated safely. Handle new_buffer; - if (old_buffer->is_shared()) { + if (host_owned) { -+ // TODO: handle shared host-owned memory once we support it. -+ if (!host_owned->grow_callback) return -1; -+ auto new_store = host_owned->grow_callback(host_owned->callback_info, ++ // TODO: handle shared host-owned memory once the C API supports it. ++ void* new_store = old_buffer->backing_store(); ++ if (new_size > old_size) { ++ if (!host_owned->grow_callback) return -1; ++ new_store = host_owned->grow_callback(host_owned->callback_info, + old_buffer->backing_store(), old_size, new_size); -+ if (!new_store) return -1; ++ if (!new_store) return -1; ++ } + bool is_external = old_buffer->is_external(); + DCHECK(is_external); + // Disconnect buffer early so GC won't free it. From f36d3cc467c4fcaf64aa98be9c8b4d320bcefd7d Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Sat, 27 Jul 2019 15:10:03 +0200 Subject: [PATCH 07/11] Makefile --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 244468d..ef6e0e8 100644 --- a/Makefile +++ b/Makefile @@ -212,7 +212,7 @@ clean: # Building .PHONY: v8 -v8: ${V8_INCLUDE}/${WASM_V8_PATCH}.hh ${V8_SRC}/${WASM_V8_PATCH}.cc v8-patch v8-build v8-unpatch +v8: ${V8_INCLUDE}/${WASM_V8_PATCH}.hh ${V8_SRC}/${WASM_V8_PATCH}.cc v8-patch v8-build .PHONY: v8-build v8-build: @@ -251,6 +251,7 @@ ${V8_SRC}/${WASM_V8_PATCH}.cc: ${WASM_SRC}/${WASM_V8_PATCH}.cc # Check out set version .PHONY: v8-checkout v8-checkout: v8-checkout-banner ${V8_DEPOT_TOOLS} ${V8_V8} + (cd ${V8_V8}; git stash) (cd ${V8_V8}; git checkout -f master) (cd ${V8_V8}; git pull) (cd ${V8_V8}; git checkout ${V8_VERSION}) From 3037ccbe5eea150286d246de4f525454aeccb9bd Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Mon, 29 Jul 2019 10:10:03 +0200 Subject: [PATCH 08/11] Fix comment --- include/wasm-v8.hh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/wasm-v8.hh b/include/wasm-v8.hh index ff2ea32..f577b0b 100644 --- a/include/wasm-v8.hh +++ b/include/wasm-v8.hh @@ -16,7 +16,8 @@ namespace Memory { using free_callback_t = void (*)(void*, byte_t*, size_t); // Create a Memory backed by an external storage. - // For a memory type with limits.min = S, It is the callers responsibility to + // For a memory type with limits.min * page_size = S, It is the caller's + // responsibility to // * provide a readable and writable, zeroed byte array of size S // * install an inaccessible redzone address range of size redzone_size_lo(S) // right before the byte vector's address range @@ -26,7 +27,7 @@ namespace Memory { // the Memory needs to grow; it receives the current byte vector, its // current size, and the new size requested (it is an invariant that // new size > old size when invoked); it needs to return the address - // of a new byte vector with redzones installed as before, or `nullptr`to + // of a new byte vector with redzones installed as before, or `nullptr` to // reject the request; the new byte vector can be the same as the old if the // host is able to grow it in place; if not, it is the host's responsibility // to copy the contents from the old to the new vector; the additional @@ -34,11 +35,11 @@ namespace Memory { // requests for the Memory will be rejected except if the delta is zero // * optionally, provide a `free_callback` that is invoked by the engine when // the Memory is no longer needed; it receives the current byte vector and - // the current size; when invoked, the host can free the bytee vector and + // the current size; when invoked, the host can free the byte vector and // associated redzones // * optionally, provide an additional parameter that is stored by the engine // and passed on to the callbacks as their first argument; the host should - // free any associated allocation in thee `free_callback` + // free any associated allocation in the `free_callback` auto make_external( Store*, const MemoryType*, byte_t*, grow_callback_t = nullptr, free_callback_t = nullptr, void* = nullptr From 99ebdbaa2d85538f2dfcc544520e240bab40ac11 Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Tue, 30 Jul 2019 09:53:41 +0200 Subject: [PATCH 09/11] Harden allocation in example --- Makefile | 2 +- example/mmap.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ef6e0e8..f613282 100644 --- a/Makefile +++ b/Makefile @@ -235,7 +235,7 @@ v8-patch: v8-unpatch: if [ -f ${V8_V8}/BUILD.gn.save ]; then \ cd ${V8_V8}; \ - patch -p1 -R <../../patch/0001-BUILD.gn-add-wasm-v8-lowlevel.patch; \ + mv -f BUILD.gn.save BUILD.gn; \ patch -p1 -R <../../patch/0002-wasm-objects-add-host-owned-memory.patch; \ fi diff --git a/example/mmap.cc b/example/mmap.cc index 204e9c6..eb107c7 100644 --- a/example/mmap.cc +++ b/example/mmap.cc @@ -65,7 +65,7 @@ auto make_mem(size_t size, int fd) -> std::unique_ptr { auto data = static_cast(base) + offset; auto result = mmap( data, alloc_size, PROT_NONE, MAP_FILE | MAP_FIXED | MAP_SHARED, fd, 0); - if (result == MAP_FAILED) { + if (result == MAP_FAILED || (data != nullptr && result != data)) { std::cout << "> Error reserving memory! errno = " << errno << " (" << strerror(errno) << ")" << std::endl; exit(1); From 95dbc275c8151a3d66bef6e10279c3421da07afc Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Tue, 30 Jul 2019 13:24:01 +0200 Subject: [PATCH 10/11] Bug fix --- patch/0002-wasm-objects-add-host-owned-memory.patch | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/patch/0002-wasm-objects-add-host-owned-memory.patch b/patch/0002-wasm-objects-add-host-owned-memory.patch index 413f87b..03f72e5 100644 --- a/patch/0002-wasm-objects-add-host-owned-memory.patch +++ b/patch/0002-wasm-objects-add-host-owned-memory.patch @@ -53,12 +53,13 @@ index 27a56695c2..f24d752e34 100644 return memory_obj; } -@@ -1310,8 +1331,11 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, +@@ -1310,8 +1331,12 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "GrowMemory"); Handle old_buffer(memory_object->array_buffer(), isolate); if (old_buffer->is_shared() && !FLAG_wasm_grow_shared_memory) return -1; -+ HostOwnedStoreInfo* host_owned = -+ Managed::cast(memory_object->host_owned())->raw(); ++ auto host_owned = memory_object->host_owned().IsUndefined() ++ ? nullptr ++ : Managed::cast(memory_object->host_owned())->raw(); auto* memory_tracker = isolate->wasm_engine()->memory_tracker(); - if (!memory_tracker->IsWasmMemoryGrowable(old_buffer)) return -1; + if (!(host_owned || memory_tracker->IsWasmMemoryGrowable(old_buffer))) @@ -66,7 +67,7 @@ index 27a56695c2..f24d752e34 100644 // Checks for maximum memory size, compute new size. uint32_t maximum_pages = wasm::max_mem_pages(); -@@ -1335,7 +1359,24 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, +@@ -1335,7 +1360,24 @@ int32_t WasmMemoryObject::Grow(Isolate* isolate, // Handle this in the interrupt handler so that it's safe for all the isolates // that share this buffer to be updated safely. Handle new_buffer; From 4eba1955fa063676fcd3413c3dbafbfb5bc6bd23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Tue, 6 Aug 2019 01:06:12 -0700 Subject: [PATCH 11/11] host-mem fixes (#101) --- example/mmap.c | 4 + example/mmap.cc | 141 ++++++++++++------ ...2-wasm-objects-add-host-owned-memory.patch | 14 +- 3 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 example/mmap.c diff --git a/example/mmap.c b/example/mmap.c new file mode 100644 index 0000000..996f2af --- /dev/null +++ b/example/mmap.c @@ -0,0 +1,4 @@ +// TODO: implement me! +int main() { + return 0; +} diff --git a/example/mmap.cc b/example/mmap.cc index eb107c7..809571e 100644 --- a/example/mmap.cc +++ b/example/mmap.cc @@ -1,26 +1,26 @@ -#include +#include +#include +#include #include +#include +#include +#include #include -#include -#include -#include #include -#include #include +#include -#include "wasm.hh" #include "wasm-v8.hh" - +#include "wasm.hh" static const char data_file[] = "mmap.data"; int mem_count = 0; -auto open_mem_file(int pages) -> int { +auto open_mem_file(size_t size) -> int { // Create memory file. - std::cout << "opening memory file..." << std::endl; - auto size = pages * wasm::Memory::page_size; + std::cout << "Opening memory file..." << std::endl; auto fd = open(data_file, O_RDWR | O_CREAT, 0600); if (fd == -1) { @@ -39,46 +39,94 @@ auto open_mem_file(int pages) -> int { } struct mem_info { - void* base; - byte_t* data; - size_t alloc_size; + void *base; + byte_t *data; + size_t alloc_total_size; int fd; }; auto make_mem(size_t size, int fd) -> std::unique_ptr { std::cout << "> Making memory " - << "(size = " << size << ", fd = " << fd << ")..." << std::endl; - auto offset = wasm::v8::Memory::redzone_size_lo(size); - auto alloc_size = size + wasm::v8::Memory::redzone_size_hi(size); - - void* base = nullptr; - if (offset > 0) { - // Lo redzone needed. - base = mmap(nullptr, offset, PROT_NONE, MAP_ANON | MAP_PRIVATE, 0, 0); - if (base == MAP_FAILED) { - std::cout << "> Error reserving lo redzone! errno = " << errno - << " (" << strerror(errno) << ")" << std::endl; - exit(1); - } + << "(size = " << size << ", fd = " << fd << ")..." << std::endl; + // How much memory we need to reserve including guard regions before and after + auto alloc_total_size = wasm::v8::Memory::redzone_size_lo(size) + size + + wasm::v8::Memory::redzone_size_hi(size); + std::cout + << std::hex + << "> linux_page_size = 0x" + << getpagesize() + << ", wasm_page_size = 0x" + << wasm::Memory::page_size + << ", memory_size = 0x" + << size + << ", redzone_size_lo = 0x" + << wasm::v8::Memory::redzone_size_lo(size) + << ", redzone_size_hi = 0x" + << wasm::v8::Memory::redzone_size_hi(size) + << ", total_size = 0x" + << alloc_total_size + << std::dec + << std::endl; + + std::cout + << "> Calling mmap " + << "(addr = nullptr" + << ", size = " + << alloc_total_size + << ")..." + << std::endl; + + // Need to allocate the total address space first and only then remap a + // portion of it with MAP_FIXED + void *base = + mmap(nullptr, alloc_total_size, PROT_NONE, MAP_ANON | MAP_PRIVATE, 0, 0); + if (base == MAP_FAILED) { + std::cout << "> Error reserving lo redzone! errno = " << errno << " (" + << strerror(errno) << ")" << std::endl; + exit(1); } - auto data = static_cast(base) + offset; - auto result = mmap( - data, alloc_size, PROT_NONE, MAP_FILE | MAP_FIXED | MAP_SHARED, fd, 0); + // Where the module data is located + auto data = + static_cast(base) + wasm::v8::Memory::redzone_size_lo(size); + + std::cout + << "> base_address = " + << static_cast(base) + << ", data_address = " + << static_cast(data) + << std::endl; + + std::cout + << "> Calling mmap " + << "(addr = " + << static_cast(data) + << ", size = " + << size + << ", fd = " + << fd + << ")..." + << std::endl; + + // Modify the existing mapping such that `size` bytes beginning at `base + + // offset` are mapped with the contents of the file descriptor `fd`. We remap + // the maximum amount of memory a module can use but only make the `size` + // bytes accessible. + auto result = mmap(data, size + wasm::v8::Memory::redzone_size_hi(size), + PROT_NONE, MAP_FIXED | MAP_SHARED, fd, 0); if (result == MAP_FAILED || (data != nullptr && result != data)) { - std::cout << "> Error reserving memory! errno = " << errno - << " (" << strerror(errno) << ")" << std::endl; + std::cout << "> Error reserving memory! errno = " << errno << " (" + << strerror(errno) << ")" << std::endl; exit(1); } if (mprotect(data, size, PROT_READ | PROT_WRITE) != 0) { std::cout << "> Error allocating memory! errno = " << errno - << " (" << strerror(errno) << ")" << std::endl; - exit(1); + << " (" << strerror(errno) << ")" << std::endl; } - ++mem_count; - return std::unique_ptr(new mem_info{base, data, alloc_size, fd}); + return std::unique_ptr( + new mem_info{base, data, alloc_total_size, fd}); } void free_mem(void* extra, byte_t* data, size_t size) { @@ -92,9 +140,9 @@ void free_mem(void* extra, byte_t* data, size_t size) { std::cout << "> Error freeing lo redzone! errno = " << errno << " (" << strerror(errno) << ")" << std::endl; exit(1); - } + } - if (munmap(info->data, info->alloc_size) == -1) { + if (munmap(info->base, info->alloc_total_size) == -1) { std::cout << "> Error freeing memory! errno = " << errno << " (" << strerror(errno) << ")" << std::endl; exit(1); @@ -232,24 +280,24 @@ void execute( // Allocate memory. std::cout << "Allocating memory..." << std::endl; auto size = pages * wasm::Memory::page_size; - auto info = make_mem(size, open_mem_file(pages)); + auto info = make_mem(size, open_mem_file(size)); // Create memory. std::cout << "Creating memory..." << std::endl; auto memory_type = wasm::MemoryType::make(wasm::Limits(pages)); + auto info_ = info.release(); auto memory = wasm::v8::Memory::make_external( - store, memory_type.get(), info->data, grow_mem, free_mem, info.release()); + store, memory_type.get(), info_->data, grow_mem, free_mem, info_); if (!memory) { std::cout << "> Error creating memory!" << std::endl; exit(1); } - info.release(); // Instantiate. std::cout << "Instantiating module..." << std::endl; auto module = wasm::Module::obtain(store, shared_module); - wasm::Extern* imports[] = {memory.get()}; + wasm::Extern *imports[] = {memory.get()}; auto instance = wasm::Instance::make(store, module.get(), imports); if (!instance) { std::cout << "> Error instantiating module!" << std::endl; @@ -265,14 +313,17 @@ void execute( std::cout << "Ending run " << run << "..." << std::endl; } - void run() { // Initialize. std::cout << "Initializing..." << std::endl; auto engine = wasm::Engine::make(); auto shared_module = compile(engine.get()); - truncate(data_file, 0); // in case it still exists + // in case it still exists + if (access(data_file, F_OK) != -1 && truncate(data_file, 0) == -1) { + perror("ftruncate"); + exit(1); + }; // Run 1. auto pages = 2; @@ -391,11 +442,9 @@ void run() { std::cout << "Shutting down..." << std::endl; } - -int main(int argc, const char* argv[]) { +int main(int argc, const char *argv[]) { run(); assert(mem_count == 0); std::cout << "Done." << std::endl; return 0; } - diff --git a/patch/0002-wasm-objects-add-host-owned-memory.patch b/patch/0002-wasm-objects-add-host-owned-memory.patch index 03f72e5..0b334b4 100644 --- a/patch/0002-wasm-objects-add-host-owned-memory.patch +++ b/patch/0002-wasm-objects-add-host-owned-memory.patch @@ -1,3 +1,15 @@ +diff --git a/src/builtins/base.tq b/src/builtins/base.tq +index 76e1a486c8..3c79ccec87 100644 +--- a/src/builtins/base.tq ++++ b/src/builtins/base.tq +@@ -1299,6 +1299,7 @@ extern class WasmTableObject extends JSObject { + extern class WasmMemoryObject extends JSObject { + array_buffer: JSArrayBuffer; + maximum_pages: Smi; ++ host_owned: Object; + instances: WeakArrayList | Undefined; + } + diff --git a/src/wasm/wasm-objects-inl.h b/src/wasm/wasm-objects-inl.h index e1fc2d2410..ec3f04be57 100644 --- a/src/wasm/wasm-objects-inl.h @@ -11,7 +23,7 @@ index e1fc2d2410..ec3f04be57 100644 // WasmGlobalObject diff --git a/src/wasm/wasm-objects.cc b/src/wasm/wasm-objects.cc -index 27a56695c2..f24d752e34 100644 +index 27a56695c2..479a93037d 100644 --- a/src/wasm/wasm-objects.cc +++ b/src/wasm/wasm-objects.cc @@ -1223,9 +1223,19 @@ void SetInstanceMemory(Handle instance,