Got rid of ASM for runtime pre_gc, post_gc and stack scan

This commit is contained in:
Egor Sheremetov 2023-09-29 19:40:01 +02:00
parent 8b073cbd48
commit b5a0b81d11
5 changed files with 35 additions and 156 deletions

View file

@ -6,21 +6,21 @@ UNIT_TESTS_FLAGS=$(TEST_FLAGS)
INVARIANTS_CHECK_FLAGS=$(TEST_FLAGS) -DFULL_INVARIANT_CHECKS
# this target is the most important one, its' artefacts should be used as a runtime of Lama
all: gc_runtime.o gc.o runtime.o
ar rc runtime.a gc_runtime.o runtime.o gc.o
all: gc.o runtime.o
ar rc runtime.a runtime.o gc.o
# this is a target that runs unit tests, scenarios are written in a single file `test_main.c`
unit_tests.o: gc.c gc.h gc_runtime.s runtime.c runtime.h runtime_common.h virt_stack.c virt_stack.h test_main.c test_util.s
$(CC) -o unit_tests.o $(UNIT_TESTS_FLAGS) gc.c gc_runtime.s virt_stack.c runtime.c test_main.c test_util.s
unit_tests.o: gc.c gc.h runtime.c runtime.h runtime_common.h virt_stack.c virt_stack.h test_main.c test_util.s
$(CC) -o unit_tests.o $(UNIT_TESTS_FLAGS) gc.c virt_stack.c runtime.c test_main.c test_util.s
# this target also runs unit tests but with additional expensive checks of GC invariants which aren't used in production version
invariants_check.o: gc.c gc.h gc_runtime.s runtime.c runtime.h runtime_common.h virt_stack.c virt_stack.h test_main.c test_util.s
$(CC) -o invariants_check.o $(INVARIANTS_CHECK_FLAGS) gc.c gc_runtime.s virt_stack.c runtime.c test_main.c test_util.s
invariants_check.o: gc.c gc.h runtime.c runtime.h runtime_common.h virt_stack.c virt_stack.h test_main.c test_util.s
$(CC) -o invariants_check.o $(INVARIANTS_CHECK_FLAGS) gc.c virt_stack.c runtime.c test_main.c test_util.s
# this target also runs unit tests but with additional expensive checks of GC invariants which aren't used in production version
# additionally, it prints debug information
invariants_check_debug_print.o: gc.c gc.h gc_runtime.s runtime.c runtime.h runtime_common.h virt_stack.c virt_stack.h test_main.c test_util.s
$(CC) -o invariants_check_debug_print.o $(INVARIANTS_CHECK_FLAGS) -DDEBUG_PRINT gc.c gc_runtime.s virt_stack.c runtime.c test_main.c test_util.s
invariants_check_debug_print.o: gc.c gc.h runtime.c runtime.h runtime_common.h virt_stack.c virt_stack.h test_main.c test_util.s
$(CC) -o invariants_check_debug_print.o $(INVARIANTS_CHECK_FLAGS) -DDEBUG_PRINT gc.c virt_stack.c runtime.c test_main.c test_util.s
virt_stack.o: virt_stack.h virt_stack.c
$(CC) $(PROD_FLAGS) -c virt_stack.c
@ -28,9 +28,6 @@ virt_stack.o: virt_stack.h virt_stack.c
gc.o: gc.c gc.h
$(CC) -rdynamic $(PROD_FLAGS) -c gc.c
gc_runtime.o: gc_runtime.s
$(CC) $(PROD_FLAGS) -c gc_runtime.s
runtime.o: runtime.c runtime.h
$(CC) $(PROD_FLAGS) -c runtime.c

View file

@ -22,7 +22,7 @@ size_t cur_id = 0;
static extra_roots_pool extra_roots;
extern size_t __gc_stack_top, __gc_stack_bottom;
size_t __gc_stack_top = 0, __gc_stack_bottom = 0;
#ifdef LAMA_ENV
extern const size_t __start_custom_data, __stop_custom_data;
#endif
@ -38,8 +38,8 @@ void dump_heap ();
#endif
void handler (int sig) {
void *array[10];
size_t size;
void *array[10];
int size;
// get void*'s for all entries on the stack
size = backtrace(array, 10);
@ -59,9 +59,8 @@ void *alloc (size_t size) {
#endif
void *p = gc_alloc_on_existing_heap(size);
if (!p) {
// not enough place in heap, need to perform GC cycle
// not enough place in the heap, need to perform GC cycle
p = gc_alloc(size);
// return gc_alloc(size);
}
return p;
}
@ -223,17 +222,23 @@ void *gc_alloc (size_t size) {
return gc_alloc_on_existing_heap(size);
}
static void gc_root_scan_stack () {
for (size_t *p = (size_t *)(__gc_stack_top + 4); p < (size_t *)__gc_stack_bottom; ++p) {
gc_test_and_mark_root((size_t **)p);
}
}
void mark_phase (void) {
#if defined(DEBUG_VERSION) && defined(DEBUG_PRINT)
fprintf(stderr, "marking has started\n");
fprintf(stderr,
"__gc_root_scan_stack has started: gc_top=%p bot=%p\n",
"gc_root_scan_stack has started: gc_top=%p bot=%p\n",
(void *)__gc_stack_top,
(void *)__gc_stack_bottom);
#endif
__gc_root_scan_stack();
gc_root_scan_stack();
#if defined(DEBUG_VERSION) && defined(DEBUG_PRINT)
fprintf(stderr, "__gc_root_scan_stack has finished\n");
fprintf(stderr, "gc_root_scan_stack has finished\n");
fprintf(stderr, "scan_extra_roots has started\n");
#endif
scan_extra_roots();
@ -554,7 +559,12 @@ extern void gc_test_and_mark_root (size_t **root) {
mark((void *)*root);
}
extern void __init (void) {
void __gc_init (void) {
__gc_stack_bottom = (size_t)__builtin_frame_address(1) + 4;
__init();
}
void __init (void) {
signal(SIGSEGV, handler);
size_t space_size = INIT_HEAP_SIZE * sizeof(size_t);

View file

@ -58,8 +58,6 @@ void *gc_alloc_on_existing_heap(size_t);
// specific for mark-and-compact_phase gc
void mark (void *obj);
void mark_phase (void);
// written in ASM, scans stack for pointers to the heap and starts marking process
extern void __gc_root_scan_stack (void);
// marks each pointer from extra roots
void scan_extra_roots (void);
#ifdef LAMA_ENV
@ -100,24 +98,16 @@ void pop_extra_root (void **p);
// ============================================================================
// MANDATORY TO CALL BEFORE ANY INTERACTION WITH GC (apart from cases where we
// are working with virtual stack as happens in tests)
extern void __gc_init (void);
void __gc_init (void);
// should be called before interaction with GC in case of using in tests with
// virtual stack, otherwise it is automatically invoked by `__gc_init`
extern void __init (void);
void __init (void);
// mostly useful for tests but basically you want to call this in case you want
// to deallocate all object allocated via GC
extern void __shutdown (void);
// Next two functions sets and unsets `__gc_stack_top`
// The first (`__pre_gc`) should be called in the very beginning of any runtime
// function during the execution of which garbage collection can be initiated.
// The last one is a `companion function` which has to be called at the very
// end of any function that previously called `__pre_gc`
extern void __pre_gc (void);
extern void __post_gc (void);
// ============================================================================
// invoked from GASM: see gc_runtime.s

View file

@ -1,113 +0,0 @@
.data
__gc_stack_bottom: .long 0
__gc_stack_top: .long 0
.globl __pre_gc
.globl __post_gc
.globl __gc_init
.globl __gc_root_scan_stack
.globl __gc_stack_top
.globl __gc_stack_bottom
.extern __init
.extern gc_test_and_mark_root
.text
__gc_init:
movl %ebp, __gc_stack_bottom
addl $4, __gc_stack_bottom
call __init
ret
// if __gc_stack_top is equal to 0
// then set __gc_stack_top to %ebp
// else return
__pre_gc:
pushl %eax
movl __gc_stack_top, %eax
cmpl $0, %eax
jne __pre_gc_2
movl %ebp, %eax
// addl $8, %eax
movl %eax, __gc_stack_top
__pre_gc_2:
popl %eax
ret
// if __gc_stack_top has been set by the caller
// (i.e. it is equal to its %ebp)
// then set __gc_stack_top to 0
// else return
__post_gc:
pushl %eax
movl __gc_stack_top, %eax
cmpl %eax, %ebp
jnz __post_gc2
movl $0, __gc_stack_top
__post_gc2:
popl %eax
ret
// Scan stack for roots
// strting from __gc_stack_top
// till __gc_stack_bottom
__gc_root_scan_stack:
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %edx
movl __gc_stack_top, %eax
jmp next
loop:
movl (%eax), %ebx
// check that it is not a pointer to code section
// i.e. the following is not true:
// __executable_start <= (%eax) <= __etext
check11:
leal __executable_start, %edx
cmpl %ebx, %edx
jna check12
jmp check21
check12:
leal __etext, %edx
cmpl %ebx, %edx
jnb next
// check that it is not a pointer into the program stack
// i.e. the following is not true:
// __gc_stack_bottom <= (%eax) <= __gc_stack_top
check21:
cmpl %ebx, __gc_stack_top
jna check22
jmp loop2
check22:
cmpl %ebx, __gc_stack_bottom
jnb next
// check if it a valid pointer
// i.e. the lastest bit is set to zero
loop2:
andl $0x00000001, %ebx
jnz next
gc_run_t:
pushl %eax
pushl %eax
call gc_test_and_mark_root
addl $4, %esp
popl %eax
next:
addl $4, %eax
cmpl %eax, __gc_stack_bottom
jne loop
returnn:
movl $0, %eax
popl %edx
popl %ebx
movl %ebp, %esp
popl %ebp
ret

View file

@ -30,23 +30,18 @@ void __post_gc_subst () { }
#endif
/* end */
extern size_t __gc_stack_top, __gc_stack_bottom;
#define PRE_GC() \
bool flag = true; \
if (__gc_stack_top == 0) { flag = false; } \
__pre_gc(); \
bool flag = false; \
flag = __gc_stack_top == 0; \
if (flag) { __gc_stack_top = (size_t)__builtin_frame_address(0); } \
assert(__gc_stack_top != 0); \
assert(__builtin_frame_address(0) <= (void *)__gc_stack_top);
#define POST_GC() \
assert(__builtin_frame_address(0) <= (void *)__gc_stack_top); \
__post_gc(); \
\
if (!flag && __gc_stack_top != 0) { \
fprintf(stderr, "Moving stack???\n"); \
assert(false); \
}
extern size_t __gc_stack_top, __gc_stack_bottom;
if (flag) { __gc_stack_top = 0; }
static void vfailure (char *s, va_list args) {
fprintf(stderr, "*** FAILURE: ");