blob: 70da0c48907cf1f1b54c9172f3ad7e21e60ffdd5 [file] [log] [blame] [edit]
// Copyright 2021 Google LLC
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.
// Include first for the platform detection macros.
#include "src/xnnpack/common.h"
#if XNN_PLATFORM_WEB
#include <emscripten/emscripten.h>
#endif // XNN_PLATFORM_WEB
#if XNN_PLATFORM_WINDOWS
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <inttypes.h>
#include <windows.h>
#else
// This define needs to come first because errno include features.h and would have defined macros that lead to
// sys/mman.h not having mremap.
#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
#include <errno.h>
#if XNN_HAS_MMAP
#include <sys/mman.h>
#endif
#include <unistd.h>
#endif // XNN_PLATFORM_WINDOWS
#if XNN_PLATFORM_QURT
#include <qurt/qurt_alloc.h> // NOLINT - header provided by Hexagon toolchain
#endif
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "include/xnnpack.h"
#include "src/xnnpack/init-once.h"
#include "src/xnnpack/log.h"
#include "src/xnnpack/math.h"
#include "src/xnnpack/memory.h"
// Helpers to allocate/mmap and release memory used by both code and weights cache.
static size_t system_page_size = 0;
XNN_INIT_ONCE_GUARD(memory);
static void init_memory_config() {
// Get page size.
#if XNN_PLATFORM_WINDOWS
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
assert(sysinfo.dwPageSize != 0);
system_page_size = (size_t) sysinfo.dwPageSize;
#elif XNN_PLATFORM_QURT || !XNN_HAS_MMAP
// sysconf(_SC_PAGESIZE) will fail, but we're just using malloc anyway.
system_page_size = 4096;
#else
long result = sysconf(_SC_PAGESIZE);
if (result == -1) {
xnn_log_fatal("failed to get page size, error code: %d", errno);
}
assert(result >= 0);
system_page_size = (size_t) result;
#endif
assert(is_po2(system_page_size));
}
static size_t get_page_size() {
XNN_INIT_ONCE(memory);
return system_page_size;
}
// Maps `size` bytes of memory, returns pointer to allocation, NULL if failed.
static void* allocate_buffer(size_t size) {
xnn_log_debug("allocating buffer of size %zu", size);
assert(size % get_page_size() == 0);
#if XNN_PLATFORM_WINDOWS
void* p = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (p == NULL) {
xnn_log_error("failed to allocate %zu bytes for code/weights buffer, error code: %" PRIu32,
size, (uint32_t) GetLastError());
return NULL;
}
#elif XNN_PLATFORM_QURT
// Emulate through qurt_malloc.
void* p = qurt_malloc(size);
if (p == NULL) {
xnn_log_error("failed to allocate %zu bytes for code/weights buffer, error code: %d", size, errno);
return NULL;
}
#elif !XNN_HAS_MMAP
// Emulate through malloc.
void* p = malloc(size);
if (p == NULL) {
xnn_log_error("failed to allocate %zu bytes for code/weights buffer, error code: %d", size, errno);
return NULL;
}
#else
void* p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED) {
xnn_log_error("failed to allocate %zu bytes for code/weights buffer, error code: %d", size, errno);
return NULL;
}
#endif
return p;
}
// Releases memory previously mapped by `allocate_buffer`, returns xnn_status_success on success.
static enum xnn_status release_memory(void* start, size_t capacity) {
#if XNN_PLATFORM_WINDOWS
// We only decommitted any unused capacity, so we release all of it now.
if (!VirtualFree(start, 0, MEM_RELEASE)) {
xnn_log_error("failed to release code/weights buffer, error code: %" PRIu32, (uint32_t) GetLastError());
return xnn_status_invalid_state;
}
#elif XNN_PLATFORM_QURT
// Emulate through qurt_free.
qurt_free(start);
#elif !XNN_HAS_MMAP
// Emulate through free.
free(start);
#else
if (munmap(start, capacity) == -1) {
xnn_log_error("failed to release code/weights buffer, error code: %d", errno);
return xnn_status_invalid_state;
}
#endif
return xnn_status_success;
}
// Resize a buffer at old_pointer of size old_bytes to new_size. The actual new size of the resized buffer is written to
// new_capacity_out, which can be >= new_size due to page alignment requirements.
// Returns a pointer to a buffer which might be the same as old_pointer if we can remap virtual memory, otherwise we
// allocate a new buffer and copy contents of old_buffer over.
static void* resize_buffer(
void* old_pointer, size_t old_size, size_t old_capacity, size_t new_size, size_t* new_capacity_out)
{
const size_t new_capacity = round_up_po2(new_size, get_page_size());
#if XNN_PLATFORM_LINUX
void* new_pointer = mremap(old_pointer, old_capacity, new_capacity, MREMAP_MAYMOVE, NULL);
if (new_pointer == MAP_FAILED) {
xnn_log_error("mremap failed with errno: %d", errno);
return NULL;
}
xnn_log_debug("resize_buffer: remap, old capacity %zu to new capacity %zu", old_capacity, new_capacity);
#else
void* new_pointer = allocate_buffer(new_capacity);
if (new_pointer == NULL) {
xnn_log_error("allocate_buffer failed");
return NULL;
}
memcpy(new_pointer, old_pointer, old_size);
// Release old code_buffer.
const enum xnn_status status = release_memory(old_pointer, old_capacity);
if (status != xnn_status_success) {
xnn_log_error("releasing old buffer failed, this could be a leak of %zu bytes", old_capacity);
// Log but proceed as per normal since we successfully allocated a new memory that can be used by the caller.
}
xnn_log_debug("resize_buffer: allocate memory, old capacity %zu to new capacity %zu", old_capacity, new_capacity);
#endif
*new_capacity_out = new_capacity;
return new_pointer;
}
// Releases unused memory. Will write the new capacity to `capacity`.
static enum xnn_status release_unused_memory(size_t size, void* start, size_t* capacity) {
// Release all unused pages.
const size_t page_aligned_size = round_up_po2(size, get_page_size());
const uint8_t* mem_start = (uint8_t*) start;
const uint8_t* unused_start = mem_start + page_aligned_size;
assert(*capacity >= page_aligned_size);
const size_t unused_capacity = *capacity - page_aligned_size;
xnn_log_debug("releasing memory, start %p, used: %zu, capacity: %zu, unused %zu", mem_start, size, *capacity,
unused_capacity);
if (unused_capacity != 0) {
// Free unused pages.
#if XNN_PLATFORM_WINDOWS
// We cannot selectively release pages inside the region of pages, so just decommit them.
if (!VirtualFree((void*) unused_start, unused_capacity, MEM_DECOMMIT)) {
xnn_log_error("failed to unmap weights buffer, error code: %" PRIu32, (uint32_t) GetLastError());
return xnn_status_invalid_state;
}
*capacity = page_aligned_size;
#elif XNN_PLATFORM_QURT || !XNN_HAS_MMAP
// We can't selectively release parts of this memory --
// we *could* release the entire block if unused_capacity == *capacity,
// but that would invalidate the start pointer. Just do nothing.
(void) unused_start;
#elif !XNN_PLATFORM_WEB
// Web does not support partial unmapping.
if (munmap((void*) unused_start, unused_capacity) == -1) {
xnn_log_error("failed to unmap weights buffer, error code: %d", errno);
return xnn_status_invalid_state;
}
*capacity = page_aligned_size;
#else
if (unused_capacity == *capacity) {
if (munmap((void*) unused_start, unused_capacity) == -1) {
xnn_log_error("failed to unmap weights buffer, error code: %d", errno);
return xnn_status_invalid_state;
} else {
*capacity = 0;
}
}
#endif
}
return xnn_status_success;
}
enum xnn_memory_permission {
xnn_memory_permission_read_only,
xnn_memory_permission_read_execute,
};
static enum xnn_status set_memory_permission(void* start, size_t size, enum xnn_memory_permission permission) {
#if XNN_PLATFORM_WINDOWS
DWORD old = 0, prot = 0;
switch (permission) {
case xnn_memory_permission_read_only:
prot = PAGE_READONLY;
break;
case xnn_memory_permission_read_execute:
prot = PAGE_EXECUTE_READ;
break;
default:
XNN_UNREACHABLE;
}
if (!VirtualProtect(start, size, prot, &old)) {
xnn_log_error(
"failed to set memory permission (%d), error code: %" PRIu32, permission, (uint32_t) GetLastError());
return xnn_status_invalid_state;
}
#elif XNN_PLATFORM_WEB || XNN_PLATFORM_QURT || !XNN_HAS_MMAP
// Memory protection not supported.
return xnn_status_success;
#else
int prot = 0;
switch (permission) {
case xnn_memory_permission_read_only:
prot = PROT_READ;
break;
case xnn_memory_permission_read_execute:
prot = PROT_READ | PROT_EXEC;
break;
default:
XNN_UNREACHABLE;
}
if (mprotect(start, size, prot) == -1) {
xnn_log_error("failed to set memory permission (%d), error code: %d", permission, errno);
return xnn_status_invalid_state;
}
#endif
return xnn_status_success;
}
enum xnn_status xnn_allocate_weights_memory(struct xnn_weights_buffer* buffer, size_t size) {
memset(buffer, 0, sizeof(struct xnn_weights_buffer));
const size_t page_aligned_size = round_up_po2(size, get_page_size());
buffer->start = allocate_buffer(page_aligned_size);
if (buffer->start == NULL) {
return xnn_status_out_of_memory;
}
buffer->size = 0;
buffer->capacity = page_aligned_size;
return xnn_status_success;
}
enum xnn_status xnn_release_weights_memory(struct xnn_weights_buffer* buffer) {
if (buffer->capacity == 0) {
return xnn_status_success;
}
const enum xnn_status status = release_memory(buffer->start, buffer->capacity);
if (status != xnn_status_success) {
return status;
}
memset(buffer, 0, sizeof(struct xnn_weights_buffer));
return xnn_status_success;
}
enum xnn_status xnn_reserve_weights_memory(struct xnn_weights_buffer* buffer, size_t min_available_size) {
if (buffer->size + min_available_size <= buffer->capacity) {
xnn_log_debug("reserving weights memory of size %zu without growing buffer", min_available_size);
return xnn_status_success;
}
size_t new_capacity = 0;
void* new_start =
resize_buffer(buffer->start, buffer->size, buffer->capacity, buffer->size + min_available_size, &new_capacity);
if (new_start == NULL) {
xnn_log_error("failed to reserve weights memory");
return xnn_status_out_of_memory;
}
buffer->start = new_start;
buffer->capacity = new_capacity;
return xnn_status_success;
}
enum xnn_status xnn_finalize_weights_memory(struct xnn_weights_buffer* buffer) {
const enum xnn_status status = release_unused_memory(buffer->size, buffer->start, &buffer->capacity);
if (status != xnn_status_success) {
return status;
}
if (buffer->capacity == 0) {
return xnn_status_success;
}
return set_memory_permission(buffer->start, buffer->size, xnn_memory_permission_read_only);
}