Files
core/test/native-runtime-abi/managed-driver.c
T

117 lines
3.9 KiB
C

#include "../../crates/dever-backend-bridge/include/runtime.h"
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h>
#include <pthread.h>
#include <string.h>
extern int dever_test_run(void);
#ifndef DEVER_TEST_ITERATIONS
#define DEVER_TEST_ITERATIONS 64
#endif
/* Link-time wrappers observe allocations in this fixture's static runtime.
* This is a bounded ownership regression, not a general memory profiler. */
static _Atomic long long live_allocations;
void *__real_malloc(size_t size);
void *__real_calloc(size_t count, size_t size);
void *__real_realloc(void *pointer, size_t size);
void __real_free(void *pointer);
int __real_posix_memalign(void **pointer, size_t alignment, size_t size);
char *__real_realpath(const char *path, char *resolved);
/* glibc allocates this returned owner inside its DSO, bypassing --wrap=malloc.
* Rust canonicalize frees it through the executable's wrapped free. */
char *__wrap_realpath(const char *path, char *resolved) {
char *pointer = __real_realpath(path, resolved);
atomic_fetch_add(&live_allocations, pointer != NULL && resolved == NULL);
return pointer;
}
void *__wrap_malloc(size_t size) {
void *pointer = __real_malloc(size);
atomic_fetch_add(&live_allocations, pointer != NULL);
return pointer;
}
void *__wrap_calloc(size_t count, size_t size) {
void *pointer = __real_calloc(count, size);
atomic_fetch_add(&live_allocations, pointer != NULL);
return pointer;
}
void *__wrap_realloc(void *pointer, size_t size) {
int had_pointer = pointer != NULL;
void *resized = __real_realloc(pointer, size);
if (!had_pointer && resized != NULL) {
atomic_fetch_add(&live_allocations, 1);
} else if (had_pointer && resized == NULL && size == 0) {
atomic_fetch_sub(&live_allocations, 1);
}
return resized;
}
void __wrap_free(void *pointer) {
atomic_fetch_sub(&live_allocations, pointer != NULL);
__real_free(pointer);
}
int __wrap_posix_memalign(void **pointer, size_t alignment, size_t size) {
int status = __real_posix_memalign(pointer, alignment, size);
atomic_fetch_add(&live_allocations, status == 0 && *pointer != NULL);
return status;
}
/* Standard-library queue caches live until the host thread exits. Join before
* measuring so lazy TLS initialization cannot masquerade as a per-call leak. */
static void *invoke_kernel(void *result) {
*(int *)result = dever_test_run();
return NULL;
}
static int run_kernel(void) {
pthread_t thread;
int result = 0;
int status = pthread_create(&thread, NULL, invoke_kernel, &result);
if (status == 0) {
status = pthread_join(thread, NULL);
}
if (status != 0) {
fprintf(stderr, "managed kernel thread failed: %s\n", strerror(status));
exit(EXIT_FAILURE);
}
return result;
}
int main(void) {
/* This C process owns signal policy just like the generated process main. */
if (dever_rt_v1_process_init() != DEVER_RT_OK) {
fprintf(stderr, "could not initialize the managed kernel process\n");
return 1;
}
/* Permit process-wide lazy initialization, then require no per-call leak. */
int result = run_kernel();
if (result != 0) {
fprintf(stderr, "managed kernel returned an unexpected result: %d\n", result);
return 1;
}
long long baseline = atomic_load(&live_allocations);
for (size_t remaining = DEVER_TEST_ITERATIONS; remaining != 0; --remaining) {
result = run_kernel();
if (result != 0) {
fprintf(stderr, "managed kernel changed its result on repeated execution: %d\n", result);
return 1;
}
long long current = atomic_load(&live_allocations);
if (current != baseline) {
fprintf(stderr, "managed kernel allocation imbalance: %lld\n",
current - baseline);
return 1;
}
}
return 0;
}