diff options
Diffstat (limited to 'libguile/whippet/src/gc-platform-gnu-linux.c')
-rw-r--r-- | libguile/whippet/src/gc-platform-gnu-linux.c | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/libguile/whippet/src/gc-platform-gnu-linux.c b/libguile/whippet/src/gc-platform-gnu-linux.c new file mode 100644 index 000000000..3ace1890d --- /dev/null +++ b/libguile/whippet/src/gc-platform-gnu-linux.c @@ -0,0 +1,211 @@ +// For pthread_getattr_np. +#define _GNU_SOURCE +#include <errno.h> +#include <link.h> +#include <pthread.h> +#include <sched.h> +#include <stdio.h> +#include <sys/mman.h> +#include <time.h> +#include <unistd.h> + +#define GC_IMPL 1 + +#include "debug.h" +#include "gc-align.h" +#include "gc-assert.h" +#include "gc-inline.h" +#include "gc-platform.h" + +void gc_platform_init(void) { + // Nothing to do. +} + +static uintptr_t fallback_current_thread_stack_base(void) GC_NEVER_INLINE; +static uintptr_t fallback_current_thread_stack_base(void) { + // Sloppily assume that there are very few frames between us and the + // thread entry or main function, and that therefore we haven't + // consumed more than a page of stack; we can then just round up the + // stack pointer to the page boundary. + fprintf(stderr, + "Using fallback strategy to capture stack base for thread %p.\n", + (void*)pthread_self()); + int local; + uintptr_t hot = (uintptr_t)&local; + size_t page_size = getpagesize(); + return (hot + page_size) & ~(page_size - 1); +} + +uintptr_t gc_platform_current_thread_stack_base(void) { + pthread_t me = pthread_self(); + pthread_attr_t attr; + int err = pthread_getattr_np(me, &attr); + if (err) { + errno = err; + // This case can occur for the main thread when running in a + // filesystem without /proc/stat. + perror("Failed to capture stack base via pthread_getattr_np"); + return fallback_current_thread_stack_base(); + } + + void *stack_low_addr; + size_t stack_size; + err = pthread_attr_getstack(&attr, &stack_low_addr, &stack_size); + pthread_attr_destroy(&attr); + if (err) { + // Should never occur. + errno = err; + perror("pthread_attr_getstack"); + return fallback_current_thread_stack_base(); + } + + return (uintptr_t)stack_low_addr + stack_size; +} + +struct visit_data { + void (*f)(uintptr_t start, uintptr_t end, struct gc_heap *heap, void *data); + struct gc_heap *heap; + void *data; +}; + +static int visit_roots(struct dl_phdr_info *info, size_t size, void *data) { + struct visit_data *visit_data = data; + uintptr_t object_addr = info->dlpi_addr; + const char *object_name = info->dlpi_name; + const ElfW(Phdr) *program_headers = info->dlpi_phdr; + size_t program_headers_count = info->dlpi_phnum; + + // From the loader's perspective, an ELF image is broken up into + // "segments", each of which is described by a "program header". + // Treat all writable data segments as potential edges into the + // GC-managed heap. + // + // Note that there are some RELRO segments which are initially + // writable but then remapped read-only. BDW-GC will exclude these, + // but we just punt for the time being and treat them as roots + for (size_t i = 0; i < program_headers_count; i++) { + const ElfW(Phdr) *p = &program_headers[i]; + if (p->p_type == PT_LOAD && (p->p_flags & PF_W)) { + uintptr_t start = p->p_vaddr + object_addr; + uintptr_t end = start + p->p_memsz; + DEBUG("found roots for '%s': [%p,%p)\n", object_name, + (void*)start, (void*)end); + visit_data->f(start, end, visit_data->heap, visit_data->data); + } + } + + return 0; +} + +void gc_platform_visit_global_conservative_roots(void (*f)(uintptr_t start, + uintptr_t end, + struct gc_heap*, + void *data), + struct gc_heap *heap, + void *data) { + struct visit_data visit_data = { f, heap, data }; + dl_iterate_phdr(visit_roots, &visit_data); +} + +int gc_platform_processor_count(void) { + cpu_set_t set; + if (sched_getaffinity(0, sizeof (set), &set) != 0) + return 1; + return CPU_COUNT(&set); +} + +uint64_t gc_platform_monotonic_nanoseconds(void) { + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts)) + GC_CRASH(); + uint64_t s = ts.tv_sec; + uint64_t ns = ts.tv_nsec; + uint64_t ns_per_sec = 1000000000; + return s * ns_per_sec + ns; +} + +size_t gc_platform_page_size(void) { + return getpagesize(); +} + +struct gc_reservation gc_platform_reserve_memory(size_t size, + size_t alignment) { + GC_ASSERT_EQ(size, align_down(size, getpagesize())); + GC_ASSERT_EQ(alignment & (alignment - 1), 0); + GC_ASSERT_EQ(alignment, align_down(alignment, getpagesize())); + + size_t extent = size + alignment; + void *mem = mmap(NULL, extent, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + + if (mem == MAP_FAILED) { + perror("failed to reserve address space"); + GC_CRASH(); + } + + uintptr_t base = (uintptr_t) mem; + uintptr_t end = base + extent; + uintptr_t aligned_base = alignment ? align_up(base, alignment) : base; + uintptr_t aligned_end = aligned_base + size; + + if (aligned_base - base) + munmap((void*)base, aligned_base - base); + if (end - aligned_end) + munmap((void*)aligned_end, end - aligned_end); + + return (struct gc_reservation){aligned_base, size}; +} + +void* +gc_platform_acquire_memory_from_reservation(struct gc_reservation reservation, + size_t offset, size_t size) { + GC_ASSERT_EQ(size, align_down(size, getpagesize())); + GC_ASSERT(size <= reservation.size); + GC_ASSERT(offset <= reservation.size - size); + + void *mem = mmap((void*)(reservation.base + offset), size, + PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + if (mem == MAP_FAILED) { + perror("mmap failed"); + return NULL; + } + + return mem; +} + +void +gc_platform_release_reservation(struct gc_reservation reservation) { + if (munmap((void*)reservation.base, reservation.size) != 0) + perror("failed to unmap memory"); +} + +void* +gc_platform_acquire_memory(size_t size, size_t alignment) { + struct gc_reservation reservation = + gc_platform_reserve_memory(size, alignment); + return gc_platform_acquire_memory_from_reservation(reservation, 0, size); +} + +void gc_platform_release_memory(void *ptr, size_t size) { + GC_ASSERT_EQ((uintptr_t)ptr, align_down((uintptr_t)ptr, getpagesize())); + GC_ASSERT_EQ(size, align_down(size, getpagesize())); + if (munmap(ptr, size) != 0) + perror("failed to unmap memory"); +} + +int gc_platform_populate_memory(void *ptr, size_t size) { + GC_ASSERT_EQ((uintptr_t)ptr, align_down((uintptr_t)ptr, getpagesize())); + GC_ASSERT_EQ(size, align_down(size, getpagesize())); + if (madvise(ptr, size, MADV_WILLNEED) == 0) + return 1; + perror("failed to populate memory"); + return 0; +} + +int gc_platform_discard_memory(void *ptr, size_t size) { + GC_ASSERT_EQ((uintptr_t)ptr, align_down((uintptr_t)ptr, getpagesize())); + GC_ASSERT_EQ(size, align_down(size, getpagesize())); + if (madvise(ptr, size, MADV_DONTNEED) == 0) + return 1; + perror("failed to discard memory"); + return 0; +} |