blob: 5955690a21cb4932fdca0bcdc2cc73fc5393560f [file]
/*
* 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;
}
}