blob: 23fa23d48b90f63affd146b5a9912af5b50dcdf8 [file] [edit]
From: rive
Subject: [PATCH 0038] grow a linear memory without copying it
Our build compiles the generic os_mremap on every platform, which maps a new
region, copies the whole memory into it and unmaps the old one. Each time a
memory outgrew its reservation the frame stalled for as long as the copy took,
about 14 ms at 256 MB and 60 ms at 1 GB on an M4 Pro. Apple now extends the
mapping in place when the addresses after it are free, else remaps the pages
into a larger region without copying; Linux and Android use mremap. On macOS
os_mmap marks every map MAP_JIT for code, and the kernel refuses to remap JIT
pages, so linear memories now map plainly there. Thread sanitizer builds keep
the copy, since the sanitizer does not see pages move.
--- a/core/iwasm/common/wasm_memory.c
+++ b/core/iwasm/common/wasm_memory.c
@@ -1520,8 +1520,18 @@
new_mem = os_mremap(mapped_mem, old_size, new_size);
}
else {
+#if defined(__APPLE__)
+ /* rive patch: os_mmap adds MAP_JIT on macOS for code, and the kernel
+ will not remap JIT pages, so a linear memory maps plainly */
+ new_mem = mmap(NULL, new_size, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1,
+ 0);
+ if (new_mem == MAP_FAILED) {
+ new_mem = NULL;
+ }
+#else
new_mem = os_mmap(NULL, new_size, MMAP_PROT_NONE, MMAP_MAP_NONE,
os_get_invalid_handle());
+#endif
}
if (!new_mem) {
return NULL;
--- a/core/shared/platform/common/memory/mremap.c
+++ b/core/shared/platform/common/memory/mremap.c
@@ -3,10 +3,66 @@
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*/
+/* rive patch: glibc declares mremap only with _GNU_SOURCE */
+#if defined(__linux__) && !defined(_GNU_SOURCE)
+#define _GNU_SOURCE
+#endif
+
#include "platform_api_vmcore.h"
+/* rive patch: thread sanitizer misses pages that move, then reports races
+ between memories that only reused an address */
+#if defined(__SANITIZE_THREAD__)
+#define RIVE_COPY_ON_GROW 1
+#elif defined(__has_feature)
+#if __has_feature(thread_sanitizer)
+#define RIVE_COPY_ON_GROW 1
+#endif
+#endif
+
+#if defined(__APPLE__) && !defined(RIVE_COPY_ON_GROW)
+#include <mach/mach.h>
+#include <sys/mman.h>
+#elif defined(__linux__) && !defined(RIVE_COPY_ON_GROW)
+#include <sys/mman.h>
+#endif
+
+/* rive patch: a copy stalls a frame in proportion to the memory's size, so
+ extend in place or move the pages, copying only when both are refused */
void *
os_mremap(void *old_addr, size_t old_size, size_t new_size)
{
+#if defined(__APPLE__) && !defined(RIVE_COPY_ON_GROW)
+ vm_address_t tail = (vm_address_t)old_addr + old_size;
+ vm_address_t moved = 0, head;
+ vm_prot_t cur_prot, max_prot;
+
+ if (vm_allocate(mach_task_self(), &tail, new_size - old_size,
+ VM_FLAGS_FIXED)
+ == KERN_SUCCESS) {
+ return old_addr;
+ }
+ if (vm_allocate(mach_task_self(), &moved, new_size, VM_FLAGS_ANYWHERE)
+ == KERN_SUCCESS) {
+ head = moved;
+ /* copy into this plain map, not os_mremap_slow's JIT one, so later
+ growth can still remap */
+ if (vm_remap(mach_task_self(), &head, old_size, 0,
+ VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, mach_task_self(),
+ (vm_address_t)old_addr, FALSE, &cur_prot, &max_prot,
+ VM_INHERIT_DEFAULT)
+ != KERN_SUCCESS) {
+ memcpy((void *)moved, old_addr, old_size);
+ }
+ munmap(old_addr, old_size);
+ return (void *)moved;
+ }
+#elif defined(__linux__) && !defined(RIVE_COPY_ON_GROW)
+ void *moved = mremap(old_addr, old_size, new_size, MREMAP_MAYMOVE);
+
+ if (moved != MAP_FAILED) {
+ return moved;
+ }
+#endif
return os_mremap_slow(old_addr, old_size, new_size);
}