[PATCH 15/19] util/mmap-alloc: Add qemu_ram_mmap implementation for emscripten

Kohei Tokunaga posted 19 patches 8 months ago
There is a newer version of this series
[PATCH 15/19] util/mmap-alloc: Add qemu_ram_mmap implementation for emscripten
Posted by Kohei Tokunaga 8 months ago
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
---
 util/mmap-alloc.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/util/mmap-alloc.c b/util/mmap-alloc.c
index ed14f9c64d..91f33682e8 100644
--- a/util/mmap-alloc.c
+++ b/util/mmap-alloc.c
@@ -145,6 +145,7 @@ static bool map_noreserve_effective(int fd, uint32_t qemu_map_flags)
     return false;
 }
 
+#ifndef EMSCRIPTEN
 /*
  * Reserve a new memory region of the requested size to be used for mapping
  * from the given fd (if any).
@@ -176,6 +177,7 @@ static void *mmap_reserve(size_t size, int fd)
 
     return mmap(0, size, PROT_NONE, flags, fd, 0);
 }
+#endif
 
 /*
  * Activate memory in a reserved region from the given fd (if any), to make
@@ -244,6 +246,21 @@ static inline size_t mmap_guard_pagesize(int fd)
 #endif
 }
 
+#ifdef EMSCRIPTEN
+void *qemu_ram_mmap(int fd,
+                    size_t size,
+                    size_t align,
+                    uint32_t qemu_map_flags,
+                    off_t map_offset)
+{
+    /*
+     * emscripten doesn't support non-zero first argument for mmap so
+     * mmap a larger region without the hint and return an aligned pointer.
+     */
+    void *ptr = mmap_activate(0, size + align, fd, qemu_map_flags, map_offset);
+    return (void *)QEMU_ALIGN_UP((uintptr_t)ptr, align);
+}
+#else
 void *qemu_ram_mmap(int fd,
                     size_t size,
                     size_t align,
@@ -293,6 +310,7 @@ void *qemu_ram_mmap(int fd,
 
     return ptr;
 }
+#endif /* EMSCRIPTEN */
 
 void qemu_ram_munmap(int fd, void *ptr, size_t size)
 {
-- 
2.25.1
Re: [PATCH 15/19] util/mmap-alloc: Add qemu_ram_mmap implementation for emscripten
Posted by Philippe Mathieu-Daudé 8 months ago
On 16/4/25 10:14, Kohei Tokunaga wrote:
> Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
> ---
>   util/mmap-alloc.c | 18 ++++++++++++++++++
>   1 file changed, 18 insertions(+)
> 
> diff --git a/util/mmap-alloc.c b/util/mmap-alloc.c
> index ed14f9c64d..91f33682e8 100644
> --- a/util/mmap-alloc.c
> +++ b/util/mmap-alloc.c
> @@ -145,6 +145,7 @@ static bool map_noreserve_effective(int fd, uint32_t qemu_map_flags)
>       return false;
>   }
>   
> +#ifndef EMSCRIPTEN
>   /*
>    * Reserve a new memory region of the requested size to be used for mapping
>    * from the given fd (if any).
> @@ -176,6 +177,7 @@ static void *mmap_reserve(size_t size, int fd)
>   
>       return mmap(0, size, PROT_NONE, flags, fd, 0);
>   }
> +#endif
>   
>   /*
>    * Activate memory in a reserved region from the given fd (if any), to make
> @@ -244,6 +246,21 @@ static inline size_t mmap_guard_pagesize(int fd)
>   #endif
>   }
>   
> +#ifdef EMSCRIPTEN
> +void *qemu_ram_mmap(int fd,
> +                    size_t size,
> +                    size_t align,
> +                    uint32_t qemu_map_flags,
> +                    off_t map_offset)
> +{
> +    /*
> +     * emscripten doesn't support non-zero first argument for mmap so
> +     * mmap a larger region without the hint and return an aligned pointer.
> +     */
> +    void *ptr = mmap_activate(0, size + align, fd, qemu_map_flags, map_offset);
> +    return (void *)QEMU_ALIGN_UP((uintptr_t)ptr, align);
> +}
> +#else
>   void *qemu_ram_mmap(int fd,
>                       size_t size,
>                       size_t align,
> @@ -293,6 +310,7 @@ void *qemu_ram_mmap(int fd,
>   
>       return ptr;
>   }
> +#endif /* EMSCRIPTEN */
>   
>   void qemu_ram_munmap(int fd, void *ptr, size_t size)
>   {

Can we keep this code generic? I.e. with something in the lines
of (only build-tested):

-- >8 --
diff --git a/util/mmap-alloc.c b/util/mmap-alloc.c
index ed14f9c64de..0e52cce5b29 100644
--- a/util/mmap-alloc.c
+++ b/util/mmap-alloc.c
@@ -238,3 +238,10 @@ static inline size_t mmap_guard_pagesize(int fd)
  {
-#if defined(__powerpc64__) && defined(__linux__)
+#if defined(EMSCRIPTEN)
+    /*
+     * emscripten doesn't support non-zero first argument for mmap so we
+     * don't use any guard, returning 0 to mmap a larger region without the
+     * hint and return an aligned pointer in qemu_ram_mmap().
+     */
+    return 0;
+#elif defined(__powerpc64__) && defined(__linux__)
      /* Mappings in the same segment must share the same page size */
@@ -246,2 +253,3 @@ static inline size_t mmap_guard_pagesize(int fd)

+
  void *qemu_ram_mmap(int fd,
@@ -253,4 +261,8 @@ void *qemu_ram_mmap(int fd,
      const size_t guard_pagesize = mmap_guard_pagesize(fd);
-    size_t offset, total;
-    void *ptr, *guardptr;
+    size_t offset = 0, total;
+    void *ptr, *guardptr = NULL;
+
+    assert(is_power_of_2(align));
+    /* Always align to host page size */
+    assert(align >= guard_pagesize);

@@ -262,13 +274,11 @@ void *qemu_ram_mmap(int fd,

-    guardptr = mmap_reserve(total, fd);
-    if (guardptr == MAP_FAILED) {
-        return MAP_FAILED;
+    if (guard_pagesize) {
+        guardptr = mmap_reserve(total, fd);
+        if (guardptr == MAP_FAILED) {
+            return MAP_FAILED;
+        }
+
+        offset = QEMU_ALIGN_UP((uintptr_t)guardptr, align) - 
(uintptr_t)guardptr;
      }

-    assert(is_power_of_2(align));
-    /* Always align to host page size */
-    assert(align >= guard_pagesize);
-
-    offset = QEMU_ALIGN_UP((uintptr_t)guardptr, align) - 
(uintptr_t)guardptr;
-
      ptr = mmap_activate(guardptr + offset, size, fd, qemu_map_flags,
---
Re: [PATCH 15/19] util/mmap-alloc: Add qemu_ram_mmap implementation for emscripten
Posted by Kohei Tokunaga 8 months ago
Hi Philippe,

> Can we keep this code generic? I.e. with something in the lines
> of (only build-tested):

Thank you for the suggestion. I'll try this approach.
Re: [PATCH 15/19] util/mmap-alloc: Add qemu_ram_mmap implementation for emscripten
Posted by Kohei Tokunaga 8 months ago
Hi Philippe,

While working on mmap-alloc.c, I found that Emscripten does not support
partial unmapping of memory regions [1]. This limitation prevents correct
implementation of qemu_ram_mmap and qemu_ram_munmap, which rely on partial
unmap behavior.

[1]
https://github.com/emscripten-core/emscripten/blob/d4a74336f23214bf3304d9eb0d03966786b30a36/system/lib/libc/emscripten_mmap.c#L61

As a workaround, I'm considering excluding mmap-alloc.c from the Emscripten
build. Instead, for Emscripten build, we can modify qemu_anon_ram_alloc (in
oslib-posix.c) to use qemu_memalign in place of qemu_ram_mmap, and disable
memory backends that rely on mmap, such as memory-backend-file and
memory-backend-shm.

I plan to include this fix in the next version of the patch series. I'm
happy to hear any suggestions if there is a better way to address this
limitation.

The change in oslib-posix.c will look like this:

diff --git a/util/oslib-posix.c b/util/oslib-posix.c
index a697c602c6..8eb0e53458 100644
--- a/util/oslib-posix.c
+++ b/util/oslib-posix.c
@@ -58,6 +58,7 @@
 #include <lwp.h>
 #endif

+#include "qemu/memalign.h"
 #include "qemu/mmap-alloc.h"

 #define MAX_MEM_PREALLOC_THREAD_COUNT 16
@@ -210,11 +211,21 @@ void *qemu_anon_ram_alloc(size_t size, uint64_t
*alignment, bool shared,
     const uint32_t qemu_map_flags = (shared ? QEMU_MAP_SHARED : 0) |
                                     (noreserve ? QEMU_MAP_NORESERVE : 0);
     size_t align = QEMU_VMALLOC_ALIGN;
+#ifndef EMSCRIPTEN
     void *ptr = qemu_ram_mmap(-1, size, align, qemu_map_flags, 0);

     if (ptr == MAP_FAILED) {
         return NULL;
     }
+#else
+    /*
+     * qemu_ram_mmap is not implemented for Emscripten. Use qemu_memalign
+     * for the anonymous allocation. noreserve is ignored as there is no
swap
+     * space on Emscripten, and shared is ignored as there is no other
+     * processes on Emscripten.
+     */
+    void *ptr = qemu_memalign(align, size);
+#endif

     if (alignment) {
         *alignment = align;
@@ -227,7 +238,16 @@ void *qemu_anon_ram_alloc(size_t size, uint64_t
*alignment, bool shared,
 void qemu_anon_ram_free(void *ptr, size_t size)
 {
     trace_qemu_anon_ram_free(ptr, size);
+#ifndef EMSCRIPTEN
     qemu_ram_munmap(-1, ptr, size);
+#else
+    /*
+     * qemu_ram_munmap is not implemented for Emscripten and qemu_memalign
+     * was used for the allocation. Use the corresponding freeing function
+     * here.
+     */
+    qemu_vfree(ptr);
+#endif
 }

 void qemu_socket_set_block(int fd)
@@ -588,7 +608,15 @@ bool qemu_prealloc_mem(int fd, char *area, size_t sz,
int max_threads,
 {
     static gsize initialized;
     int ret;
+#ifndef EMSCRIPTEN
     size_t hpagesize = qemu_fd_getpagesize(fd);
+#else
+    /*
+     * mmap-alloc.c is excluded from Emscripten build, so
qemu_fd_getpagesize
+     * is unavailable. Fallback to the lower level implementation.
+     */
+    size_t hpagesize = qemu_real_host_page_size();
+#endif
     size_t numpages = DIV_ROUND_UP(sz, hpagesize);
     bool use_madv_populate_write;
     struct sigaction act;