| // Copyright 2019 The Marl Authors. | |
| // | |
| // 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 | |
| // | |
| // https://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 "marl/memory.h" | |
| #include "marl/debug.h" | |
| #include "marl/sanitizers.h" | |
| #include <cstring> | |
| #if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) || defined(__EMSCRIPTEN__) | |
| #include <sys/mman.h> | |
| #include <unistd.h> | |
| namespace { | |
| // This was a static in pageSize(), but due to the following TSAN false-positive | |
| // bug, this has been moved out to a global. | |
| // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68338 | |
| const size_t kPageSize = sysconf(_SC_PAGESIZE); | |
| inline size_t pageSize() { | |
| return kPageSize; | |
| } | |
| inline void* allocatePages(size_t count) { | |
| auto mapping = mmap(nullptr, count * pageSize(), PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | |
| MARL_ASSERT(mapping != MAP_FAILED, "Failed to allocate %d pages", int(count)); | |
| if (mapping == MAP_FAILED) { | |
| mapping = nullptr; | |
| } | |
| return mapping; | |
| } | |
| inline void freePages(void* ptr, size_t count) { | |
| auto res = munmap(ptr, count * pageSize()); | |
| (void)res; | |
| MARL_ASSERT(res == 0, "Failed to free %d pages at %p", int(count), ptr); | |
| } | |
| inline void protectPage(void* addr) { | |
| auto res = mprotect(addr, pageSize(), PROT_NONE); | |
| (void)res; | |
| MARL_ASSERT(res == 0, "Failed to protect page at %p", addr); | |
| } | |
| } // anonymous namespace | |
| #elif defined(__Fuchsia__) | |
| #include <unistd.h> | |
| #include <zircon/process.h> | |
| #include <zircon/syscalls.h> | |
| namespace { | |
| // This was a static in pageSize(), but due to the following TSAN false-positive | |
| // bug, this has been moved out to a global. | |
| // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68338 | |
| const size_t kPageSize = sysconf(_SC_PAGESIZE); | |
| inline size_t pageSize() { | |
| return kPageSize; | |
| } | |
| inline void* allocatePages(size_t count) { | |
| auto length = count * kPageSize; | |
| zx_handle_t vmo; | |
| if (zx_vmo_create(length, 0, &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); | |
| (void)status; | |
| MARL_ASSERT(status == ZX_OK, "Failed to allocate %d pages", int(count)); | |
| return reinterpret_cast<void*>(reservation); | |
| } | |
| inline void freePages(void* ptr, size_t count) { | |
| auto length = count * kPageSize; | |
| zx_status_t status = zx_vmar_unmap(zx_vmar_root_self(), | |
| reinterpret_cast<zx_vaddr_t>(ptr), length); | |
| (void)status; | |
| MARL_ASSERT(status == ZX_OK, "Failed to free %d pages at %p", int(count), | |
| ptr); | |
| } | |
| inline void protectPage(void* addr) { | |
| zx_status_t status = zx_vmar_protect( | |
| zx_vmar_root_self(), 0, reinterpret_cast<zx_vaddr_t>(addr), kPageSize); | |
| (void)status; | |
| MARL_ASSERT(status == ZX_OK, "Failed to protect page at %p", addr); | |
| } | |
| } // anonymous namespace | |
| #elif defined(_WIN32) | |
| #define WIN32_LEAN_AND_MEAN 1 | |
| #include <Windows.h> | |
| namespace { | |
| inline size_t pageSize() { | |
| static auto size = [] { | |
| SYSTEM_INFO systemInfo = {}; | |
| GetSystemInfo(&systemInfo); | |
| return systemInfo.dwPageSize; | |
| }(); | |
| return size; | |
| } | |
| inline void* allocatePages(size_t count) { | |
| auto mapping = VirtualAlloc(nullptr, count * pageSize(), | |
| MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); | |
| MARL_ASSERT(mapping != nullptr, "Failed to allocate %d pages", int(count)); | |
| return mapping; | |
| } | |
| inline void freePages(void* ptr, size_t count) { | |
| (void)count; | |
| auto res = VirtualFree(ptr, 0, MEM_RELEASE); | |
| (void)res; | |
| MARL_ASSERT(res != 0, "Failed to free %d pages at %p", int(count), ptr); | |
| } | |
| inline void protectPage(void* addr) { | |
| DWORD oldVal = 0; | |
| auto res = VirtualProtect(addr, pageSize(), PAGE_NOACCESS, &oldVal); | |
| (void)res; | |
| MARL_ASSERT(res != 0, "Failed to protect page at %p", addr); | |
| } | |
| } // anonymous namespace | |
| #else | |
| #error "Page based allocation not implemented for this platform" | |
| #endif | |
| namespace { | |
| // pagedMalloc() allocates size bytes of uninitialized storage with the | |
| // specified minimum byte alignment using OS specific page mapping calls. | |
| // If guardLow is true then reads or writes to the page below the returned | |
| // address will cause a page fault. | |
| // If guardHigh is true then reads or writes to the page above the allocated | |
| // block will cause a page fault. | |
| // The pointer returned must be freed with pagedFree(). | |
| void* pagedMalloc(size_t alignment, | |
| size_t size, | |
| bool guardLow, | |
| bool guardHigh) { | |
| (void)alignment; | |
| MARL_ASSERT(alignment < pageSize(), | |
| "alignment (0x%x) must be less than the page size (0x%x)", | |
| int(alignment), int(pageSize())); | |
| auto numRequestedPages = (size + pageSize() - 1) / pageSize(); | |
| auto numTotalPages = | |
| numRequestedPages + (guardLow ? 1 : 0) + (guardHigh ? 1 : 0); | |
| auto mem = reinterpret_cast<uint8_t*>(allocatePages(numTotalPages)); | |
| if (guardLow) { | |
| protectPage(mem); | |
| mem += pageSize(); | |
| } | |
| if (guardHigh) { | |
| protectPage(mem + numRequestedPages * pageSize()); | |
| } | |
| return mem; | |
| } | |
| // pagedFree() frees the memory allocated with pagedMalloc(). | |
| void pagedFree(void* ptr, | |
| size_t alignment, | |
| size_t size, | |
| bool guardLow, | |
| bool guardHigh) { | |
| (void)alignment; | |
| MARL_ASSERT(alignment < pageSize(), | |
| "alignment (0x%x) must be less than the page size (0x%x)", | |
| int(alignment), int(pageSize())); | |
| auto numRequestedPages = (size + pageSize() - 1) / pageSize(); | |
| auto numTotalPages = | |
| numRequestedPages + (guardLow ? 1 : 0) + (guardHigh ? 1 : 0); | |
| if (guardLow) { | |
| ptr = reinterpret_cast<uint8_t*>(ptr) - pageSize(); | |
| } | |
| freePages(ptr, numTotalPages); | |
| } | |
| // alignedMalloc() allocates size bytes of uninitialized storage with the | |
| // specified minimum byte alignment. The pointer returned must be freed with | |
| // alignedFree(). | |
| inline void* alignedMalloc(size_t alignment, size_t size) { | |
| size_t allocSize = size + alignment + sizeof(void*); | |
| auto allocation = malloc(allocSize); | |
| auto aligned = reinterpret_cast<uint8_t*>(marl::alignUp( | |
| reinterpret_cast<uintptr_t>(allocation), alignment)); // align | |
| memcpy(aligned + size, &allocation, sizeof(void*)); // pointer-to-allocation | |
| return aligned; | |
| } | |
| // alignedFree() frees memory allocated by alignedMalloc. | |
| inline void alignedFree(void* ptr, size_t size) { | |
| void* base; | |
| memcpy(&base, reinterpret_cast<uint8_t*>(ptr) + size, sizeof(void*)); | |
| free(base); | |
| } | |
| class DefaultAllocator : public marl::Allocator { | |
| public: | |
| static DefaultAllocator instance; | |
| virtual marl::Allocation allocate( | |
| const marl::Allocation::Request& request) override { | |
| void* ptr = nullptr; | |
| if (request.useGuards) { | |
| ptr = ::pagedMalloc(request.alignment, request.size, true, true); | |
| } else if (request.alignment > 1U) { | |
| ptr = ::alignedMalloc(request.alignment, request.size); | |
| } else { | |
| ptr = ::malloc(request.size); | |
| } | |
| MARL_ASSERT(ptr != nullptr, "Allocation failed"); | |
| MARL_ASSERT(reinterpret_cast<uintptr_t>(ptr) % request.alignment == 0, | |
| "Allocation gave incorrect alignment"); | |
| marl::Allocation allocation; | |
| allocation.ptr = ptr; | |
| allocation.request = request; | |
| return allocation; | |
| } | |
| virtual void free(const marl::Allocation& allocation) override { | |
| if (allocation.request.useGuards) { | |
| ::pagedFree(allocation.ptr, allocation.request.alignment, | |
| allocation.request.size, true, true); | |
| } else if (allocation.request.alignment > 1U) { | |
| ::alignedFree(allocation.ptr, allocation.request.size); | |
| } else { | |
| ::free(allocation.ptr); | |
| } | |
| } | |
| }; | |
| DefaultAllocator DefaultAllocator::instance; | |
| } // anonymous namespace | |
| namespace marl { | |
| Allocator* Allocator::Default = &DefaultAllocator::instance; | |
| size_t pageSize() { | |
| return ::pageSize(); | |
| } | |
| } // namespace marl |