| /* | |
| * Copyright 2026 Rive | |
| */ | |
| // The half of WAMR's platform layer that the Nintendo SDK does not cover. | |
| // nnSdk exports no mmap family, so mapping goes through nn::os virtual | |
| // address memory: AllocateAddressRegion reserves, AllocateMemoryPages commits, | |
| // SetMemoryPermission is mprotect. | |
| #include "platform_api_vmcore.h" | |
| #include <nn/nn_Result.h> | |
| #include <nn/os.h> | |
| #include <nn/os/os_MemoryHeapCommon.h> | |
| #include <nn/os/os_MemoryPermission.h> | |
| #include <nn/os/os_VirtualAddressMemoryApi.h> | |
| #include <malloc.h> | |
| #include <string.h> | |
| // Virtual address memory is opt-in per application. Without it the mapping | |
| // calls all fail, so fall back to aligned allocations from the app heap: | |
| // software bounds checks mean nothing here needs real page permissions. | |
| static bool s_virtualAddressMemory = false; | |
| static size_t roundUpToPage(size_t size) | |
| { | |
| const size_t page = nn::os::MemoryPageSize; | |
| return (size + page - 1) & ~(page - 1); | |
| } | |
| static nn::os::MemoryPermission toPermission(int prot) | |
| { | |
| if (prot & MMAP_PROT_WRITE) | |
| { | |
| return nn::os::MemoryPermission_ReadWrite; | |
| } | |
| if (prot & MMAP_PROT_READ) | |
| { | |
| return nn::os::MemoryPermission_ReadOnly; | |
| } | |
| return nn::os::MemoryPermission_None; | |
| } | |
| extern "C" | |
| { | |
| int bh_platform_init() | |
| { | |
| s_virtualAddressMemory = nn::os::IsVirtualAddressMemoryEnabled(); | |
| fprintf( | |
| stderr, | |
| "nx wamr: virtual address memory %s\n", | |
| s_virtualAddressMemory ? "enabled" : "disabled (heap fallback)"); | |
| return 0; | |
| } | |
| void bh_platform_destroy() {} | |
| int os_printf(const char* format, ...) | |
| { | |
| va_list ap; | |
| va_start(ap, format); | |
| int ret = vprintf(format, ap); | |
| va_end(ap); | |
| return ret; | |
| } | |
| int os_vprintf(const char* format, va_list ap) { return vprintf(format, ap); } | |
| int os_getpagesize(void) { return (int)nn::os::MemoryPageSize; } | |
| // WAMR's shared posix thread code calls the libc spelling directly. | |
| int getpagesize(void) { return (int)nn::os::MemoryPageSize; } | |
| void* os_mmap( | |
| void* hint, | |
| size_t size, | |
| int prot, | |
| int flags, | |
| os_file_handle file) | |
| { | |
| (void)hint; | |
| (void)flags; | |
| (void)file; | |
| size_t requestSize = roundUpToPage(size); | |
| if (requestSize < size) | |
| { | |
| return NULL; | |
| } | |
| if (!s_virtualAddressMemory) | |
| { | |
| void* p = memalign(nn::os::MemoryPageSize, requestSize); | |
| if (p != NULL) | |
| { | |
| // mmap semantics: callers, wasm linear memory above all, | |
| // depend on fresh pages reading zero. | |
| memset(p, 0, requestSize); | |
| } | |
| if (p == NULL) | |
| { | |
| fprintf( | |
| stderr, | |
| "nx wamr: memalign failed for %zu bytes\n", | |
| requestSize); | |
| } | |
| return p; | |
| } | |
| uintptr_t address = 0; | |
| if (nn::os::AllocateAddressRegion(&address, requestSize).IsFailure()) | |
| { | |
| fprintf( | |
| stderr, | |
| "nx wamr: AllocateAddressRegion failed for %zu bytes\n", | |
| requestSize); | |
| return NULL; | |
| } | |
| // Committing the whole reservation is right because software bounds | |
| // checks make WAMR reserve only what it maps. | |
| if (nn::os::AllocateMemoryPages(address, requestSize).IsFailure()) | |
| { | |
| fprintf( | |
| stderr, | |
| "nx wamr: AllocateMemoryPages failed for %zu bytes\n", | |
| requestSize); | |
| static_cast<void>(nn::os::FreeAddressRegion(address)); | |
| return NULL; | |
| } | |
| nn::os::SetMemoryPermission(address, requestSize, toPermission(prot)); | |
| return (void*)address; | |
| } | |
| void os_munmap(void* addr, size_t size) | |
| { | |
| if (addr == NULL) | |
| { | |
| return; | |
| } | |
| if (!s_virtualAddressMemory) | |
| { | |
| free(addr); | |
| return; | |
| } | |
| uintptr_t address = (uintptr_t)addr; | |
| size_t requestSize = roundUpToPage(size); | |
| // Permissions must come back before the pages can be released. | |
| nn::os::SetMemoryPermission( | |
| address, | |
| requestSize, | |
| nn::os::MemoryPermission_ReadWrite); | |
| static_cast<void>(nn::os::FreeMemoryPages(address, requestSize)); | |
| static_cast<void>(nn::os::FreeAddressRegion(address)); | |
| } | |
| int os_mprotect(void* addr, size_t size, int prot) | |
| { | |
| if (addr == NULL || !s_virtualAddressMemory) | |
| { | |
| return 0; | |
| } | |
| nn::os::SetMemoryPermission( | |
| (uintptr_t)addr, | |
| roundUpToPage(size), | |
| toPermission(prot)); | |
| return 0; | |
| } | |
| // No executable mapping exists on this platform, so nothing ever needs the | |
| // instruction stream made coherent. | |
| void os_dcache_flush(void) {} | |
| void os_icache_flush(void* start, size_t len) | |
| { | |
| (void)start; | |
| (void)len; | |
| } | |
| } |