diff --git a/Makefile b/Makefile index c610df8..f613282 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,8 @@ OUT_DIR = out # Example config EXAMPLE_OUT = ${OUT_DIR}/${EXAMPLE_DIR} EXAMPLES = \ + mmap +TEMPORARILY_DISABLED_DO_NOT_COMMIT = \ hello \ callback \ trap \ @@ -38,6 +40,7 @@ EXAMPLES = \ finalize \ serialize \ threads \ + mmap \ # multi \ # Wasm config @@ -209,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: @@ -222,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}; \ + mv -f BUILD.gn.save BUILD.gn; \ + 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 @@ -245,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}) 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 new file mode 100644 index 0000000..809571e --- /dev/null +++ b/example/mmap.cc @@ -0,0 +1,450 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "wasm-v8.hh" +#include "wasm.hh" + +static const char data_file[] = "mmap.data"; + +int mem_count = 0; + +auto open_mem_file(size_t size) -> int { + // Create memory file. + std::cout << "Opening memory file..." << std::endl; + + 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_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; + // 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); + } + + // 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; + exit(1); + } + + if (mprotect(data, size, PROT_READ | PROT_WRITE) != 0) { + std::cout << "> Error allocating memory! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + } + ++mem_count; + return std::unique_ptr( + new mem_info{base, data, alloc_total_size, fd}); +} + +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); + 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->base, info->alloc_total_size) == -1) { + std::cout << "> Error freeing memory! errno = " << errno + << " (" << strerror(errno) << ")" << std::endl; + exit(1); + } + + delete info; + --mem_count; +} + +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; + 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 + << " (" << 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(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_); + if (!memory) { + std::cout << "> Error creating memory!" << std::endl; + exit(1); + } + + // 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()); + + // 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; + 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++); + auto grow_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_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 = 5; + 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(), 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_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); + }; + 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 0000000..997e452 Binary files /dev/null and b/example/mmap.wasm differ diff --git a/example/mmap.wat b/example/mmap.wat new file mode 100644 index 0000000..cc7904e --- /dev/null +++ b/example/mmap.wat @@ -0,0 +1,10 @@ +(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)) + ) + (func (export "grow") (param i32) (result i32) (memory.grow (local.get 0))) +) diff --git a/include/wasm-v8.hh b/include/wasm-v8.hh new file mode 100644 index 0000000..f577b0b --- /dev/null +++ b/include/wasm-v8.hh @@ -0,0 +1,57 @@ +// 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 (*)(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 * 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 + // * 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 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 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 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 the `free_callback` + auto make_external( + Store*, const MemoryType*, byte_t*, + grow_callback_t = nullptr, free_callback_t = nullptr, void* = nullptr + ) -> 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..0b334b4 --- /dev/null +++ b/patch/0002-wasm-objects-add-host-owned-memory.patch @@ -0,0 +1,143 @@ +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 ++++ 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..479a93037d 100644 +--- a/src/wasm/wasm-objects.cc ++++ b/src/wasm/wasm-objects.cc +@@ -1223,9 +1223,19 @@ void SetInstanceMemory(Handle instance, + + } // namespace + ++WasmMemoryObject::HostOwnedStoreInfo::~HostOwnedStoreInfo() { ++ // 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( + 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 +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->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; + } +@@ -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; ++ 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))) ++ return -1; + + // Checks for maximum memory size, compute new size. + uint32_t maximum_pages = wasm::max_mem_pages(); +@@ -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; +- if (old_buffer->is_shared()) { ++ if (host_owned) { ++ // 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; ++ } ++ 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); ++ 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..c3ecf4efe0 100644 +--- a/src/wasm/wasm-objects.h ++++ b/src/wasm/wasm-objects.h +@@ -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*, size_t) = nullptr; ++ void* callback_info = nullptr; ++ void* data = nullptr; ++ size_t size = 0; ++ ++ ~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 +358,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..0360dbc 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*, size_t); +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..daf1b85 100644 --- a/src/wasm-v8.cc +++ b/src/wasm-v8.cc @@ -2185,3 +2185,49 @@ 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, + grow_callback_t grow, free_callback_t free, void* info + ) -> 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()); + + // 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 + +///////////////////////////////////////////////////////////////////////////////