// Copyright 2016 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ExecutableMemory.hpp"
#include "Debug.hpp"
#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <intrin.h>
#elif defined(__Fuchsia__)
#include <unistd.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#else
#include <errno.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <unistd.h>
#endif
#include <memory.h>
#undef allocate
#undef deallocate
#if (defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || defined (_M_X64)) && !defined(__x86__)
#define __x86__
#endif
namespace rr
{
namespace
{
struct Allocation
{
// size_t bytes;
unsigned char *block;
};
void *allocateRaw(size_t bytes, size_t alignment)
{
ASSERT((alignment & (alignment - 1)) == 0); // Power of 2 alignment.
#if defined(LINUX_ENABLE_NAMED_MMAP)
void *allocation;
int result = posix_memalign(&allocation, alignment, bytes);
if(result != 0)
{
errno = result;
allocation = nullptr;
}
return allocation;
#else
unsigned char *block = new unsigned char[bytes + sizeof(Allocation) + alignment];
unsigned char *aligned = nullptr;
if(block)
{
aligned = (unsigned char*)((uintptr_t)(block + sizeof(Allocation) + alignment - 1) & -(intptr_t)alignment);
Allocation *allocation = (Allocation*)(aligned - sizeof(Allocation));
// allocation->bytes = bytes;
allocation->block = block;
}
return aligned;
#endif
}
#if defined(LINUX_ENABLE_NAMED_MMAP)
// Create a file descriptor for anonymous memory with the given
// name. Returns -1 on failure.
// TODO: remove once libc wrapper exists.
int memfd_create(const char* name, unsigned int flags)
{
#if __aarch64__
#define __NR_memfd_create 279
#elif __arm__
#define __NR_memfd_create 279
#elif __powerpc64__
#define __NR_memfd_create 360
#elif __i386__
#define __NR_memfd_create 356
#elif __x86_64__
#define __NR_memfd_create 319
#endif /* __NR_memfd_create__ */
#ifdef __NR_memfd_create
// In the event of no system call this returns -1 with errno set
// as ENOSYS.
return syscall(__NR_memfd_create, name, flags);
#else
return -1;
#endif
}
// Returns a file descriptor for use with an anonymous mmap, if
// memfd_create fails, -1 is returned. Note, the mappings should be
// MAP_PRIVATE so that underlying pages aren't shared.
int anonymousFd()
{
static int fd = memfd_create("SwiftShader JIT", 0);
return fd;
}
// Ensure there is enough space in the "anonymous" fd for length.
void ensureAnonFileSize(int anonFd, size_t length)
{
static size_t fileSize = 0;
if(length > fileSize)
{
ftruncate(anonFd, length);
fileSize = length;
}
}
#endif // defined(LINUX_ENABLE_NAMED_MMAP)
} // anonymous namespace
size_t memoryPageSize()
{
static int pageSize = 0;
if(pageSize == 0)
{
#if defined(_WIN32)
SYSTEM_INFO systemInfo;
GetSystemInfo(&systemInfo);
pageSize = systemInfo.dwPageSize;
#else
pageSize = sysconf(_SC_PAGESIZE);
#endif
}
return pageSize;
}
void *allocate(size_t bytes, size_t alignment)
{
void *memory = allocateRaw(bytes, alignment);
if(memory)
{
memset(memory, 0, bytes);
}
return memory;
}
void deallocate(void *memory)
{
#if defined(LINUX_ENABLE_NAMED_MMAP)
free(memory);
#else
if(memory)
{
unsigned char *aligned = (unsigned char*)memory;
Allocation *allocation = (Allocation*)(aligned - sizeof(Allocation));
delete[] allocation->block;
}
#endif
}
// Rounds |x| up to a multiple of |m|, where |m| is a power of 2.
inline uintptr_t roundUp(uintptr_t x, uintptr_t m)
{
ASSERT(m > 0 && (m & (m - 1)) == 0); // |m| must be a power of 2.
return (x + m - 1) & ~(m - 1);
}
void *allocateExecutable(size_t bytes)
{
size_t pageSize = memoryPageSize();
size_t length = roundUp(bytes, pageSize);
void *mapping;
#if defined(LINUX_ENABLE_NAMED_MMAP)
// Try to name the memory region for the executable code,
// to aid profilers.
int anonFd = anonymousFd();
if(anonFd == -1)
{
mapping = mmap(nullptr, length, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}
else
{
ensureAnonFileSize(anonFd, length);
mapping = mmap(nullptr, length, PROT_READ | PROT_WRITE,
MAP_PRIVATE, anonFd, 0);
}
if(mapping == MAP_FAILED)
{
mapping = nullptr;
}
#elif defined(__Fuchsia__)
zx_handle_t vmo;
if (zx_vmo_create(length, ZX_VMO_NON_RESIZABLE, &vmo) != ZX_OK) {
return nullptr;
}
if (zx_vmo_replace_as_executable(vmo, ZX_HANDLE_INVALID, &vmo) != ZX_OK) {
return nullptr;
}
zx_vaddr_t reservation;
zx_status_t status = zx_vmar_map(
zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
0, vmo, 0, length, &reservation);
zx_handle_close(vmo);
if (status != ZX_OK) {
return nullptr;
}
zx_vaddr_t alignedReservation = roundUp(reservation, pageSize);
mapping = reinterpret_cast<void*>(alignedReservation);
// Unmap extra memory reserved before the block.
if (alignedReservation != reservation) {
size_t prefix_size = alignedReservation - reservation;
status =
zx_vmar_unmap(zx_vmar_root_self(), reservation, prefix_size);
ASSERT(status == ZX_OK);
length -= prefix_size;
}
// Unmap extra memory at the end.
if (length > bytes) {
status = zx_vmar_unmap(
zx_vmar_root_self(), alignedReservation + bytes,
length - bytes);
ASSERT(status == ZX_OK);
}
#else
mapping = allocate(length, pageSize);
#endif
return mapping;
}
void markExecutable(void *memory, size_t bytes)
{
#if defined(_WIN32)
unsigned long oldProtection;
VirtualProtect(memory, bytes, PAGE_EXECUTE_READ, &oldProtection);
#elif defined(__Fuchsia__)
zx_status_t status = zx_vmar_protect(
zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_EXECUTE,
reinterpret_cast<zx_vaddr_t>(memory), bytes);
ASSERT(status != ZX_OK);
#else
mprotect(memory, bytes, PROT_READ | PROT_EXEC);
#endif
}
void deallocateExecutable(void *memory, size_t bytes)
{
#if defined(_WIN32)
unsigned long oldProtection;
VirtualProtect(memory, bytes, PAGE_READWRITE, &oldProtection);
deallocate(memory);
#elif defined(LINUX_ENABLE_NAMED_MMAP)
size_t pageSize = memoryPageSize();
size_t length = (bytes + pageSize - 1) & ~(pageSize - 1);
munmap(memory, length);
#elif defined(__Fuchsia__)
zx_vmar_unmap(zx_vmar_root_self(), reinterpret_cast<zx_vaddr_t>(memory),
bytes);
#else
mprotect(memory, bytes, PROT_READ | PROT_WRITE);
deallocate(memory);
#endif
}
}