Skip to content

Commit 3ba1038

Browse files
committed
Restore fixupBoehmStackPointer
This was removed in NixOS#11152. However, we need it for the multi-threaded evaluator, because otherwise Boehm GC will crash while scanning the thread stack: #0 GC_push_all_eager (bottom=<optimized out>, top=<optimized out>) at extra/../mark.c:1488 #1 0x00007ffff74691d5 in GC_push_all_stack_sections (lo=<optimized out>, hi=<optimized out>, traced_stack_sect=0x0) at extra/../mark_rts.c:704 #2 GC_push_all_stacks () at extra/../pthread_stop_world.c:876 #3 GC_default_push_other_roots () at extra/../os_dep.c:2893 #4 0x00007ffff746235c in GC_mark_some (cold_gc_frame=0x7ffee8ecaa50 "`\304G\367\377\177") at extra/../mark.c:374 #5 0x00007ffff7465a8d in GC_stopped_mark (stop_func=stop_func@entry=0x7ffff7453c80 <GC_never_stop_func>) at extra/../alloc.c:875 #6 0x00007ffff7466724 in GC_try_to_collect_inner (stop_func=0x7ffff7453c80 <GC_never_stop_func>) at extra/../alloc.c:624 #7 0x00007ffff7466a22 in GC_collect_or_expand (needed_blocks=needed_blocks@entry=1, ignore_off_page=ignore_off_page@entry=0, retry=retry@entry=0) at extra/../alloc.c:1688 #8 0x00007ffff746878f in GC_allocobj (gran=<optimized out>, kind=<optimized out>) at extra/../alloc.c:1798 #9 GC_generic_malloc_inner (lb=<optimized out>, k=k@entry=1) at extra/../malloc.c:193 #10 0x00007ffff746cd40 in GC_generic_malloc_many (lb=<optimized out>, k=<optimized out>, result=<optimized out>) at extra/../mallocx.c:477 #11 0x00007ffff746cf35 in GC_malloc_kind (bytes=120, kind=1) at extra/../thread_local_alloc.c:187 #12 0x00007ffff796ede5 in nix::allocBytes (n=<optimized out>, n=<optimized out>) at ../src/libexpr/include/nix/expr/eval-inline.hh:19 This is because it will use the stack pointer of the coroutine, so it will scan a region of memory that doesn't exist, e.g. Stack for thread 0x7ffea4ff96c0 is [0x7ffe80197af0w,0x7ffea4ffa000) (where 0x7ffe80197af0w is the sp of the coroutine and 0x7ffea4ffa000 is the base of the thread stack). We don't scan coroutine stacks, because currently they don't have GC roots (there is no evaluation happening in coroutines). So there is currently no need to restore the other parts of the original patch, such as BoehmGCStackAllocator.
1 parent c4c3203 commit 3ba1038

File tree

1 file changed

+64
-0
lines changed

1 file changed

+64
-0
lines changed

src/libexpr/eval-gc.cc

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,67 @@ static void * oomHandler(size_t requested)
3535
throw std::bad_alloc();
3636
}
3737

38+
/**
39+
* When a thread goes into a coroutine, we lose its original sp until
40+
* control flow returns to the thread. This causes Boehm GC to crash
41+
* since it will scan memory between the coroutine's sp and the
42+
* original stack base of the thread. Therefore, we detect when the
43+
* current sp is outside of the original thread stack and push the
44+
* entire thread stack instead, as an approximation.
45+
*
46+
* This is not optimal, because it causes the stack below sp to be
47+
* scanned. However, we usually we don't have active coroutines during
48+
* evaluation, so this is acceptable.
49+
*
50+
* Note that we don't scan coroutine stacks. It's currently assumed
51+
* that we don't have GC roots in coroutines.
52+
*/
53+
void fixupBoehmStackPointer(void ** sp_ptr, void * _pthread_id)
54+
{
55+
void *& sp = *sp_ptr;
56+
auto pthread_id = reinterpret_cast<pthread_t>(_pthread_id);
57+
size_t osStackSize;
58+
// The low address of the stack, which grows down.
59+
void * osStackLimit;
60+
61+
# ifdef __APPLE__
62+
osStackSize = pthread_get_stacksize_np(pthread_id);
63+
osStackLimit = pthread_get_stackaddr_np(pthread_id);
64+
# else
65+
pthread_attr_t pattr;
66+
if (pthread_attr_init(&pattr)) {
67+
throw Error("fixupBoehmStackPointer: pthread_attr_init failed");
68+
}
69+
# ifdef HAVE_PTHREAD_GETATTR_NP
70+
if (pthread_getattr_np(pthread_id, &pattr)) {
71+
throw Error("fixupBoehmStackPointer: pthread_getattr_np failed");
72+
}
73+
# elif HAVE_PTHREAD_ATTR_GET_NP
74+
if (!pthread_attr_init(&pattr)) {
75+
throw Error("fixupBoehmStackPointer: pthread_attr_init failed");
76+
}
77+
if (!pthread_attr_get_np(pthread_id, &pattr)) {
78+
throw Error("fixupBoehmStackPointer: pthread_attr_get_np failed");
79+
}
80+
# else
81+
# error "Need one of `pthread_attr_get_np` or `pthread_getattr_np`"
82+
# endif
83+
if (pthread_attr_getstack(&pattr, &osStackLimit, &osStackSize)) {
84+
throw Error("fixupBoehmStackPointer: pthread_attr_getstack failed");
85+
}
86+
if (pthread_attr_destroy(&pattr)) {
87+
throw Error("fixupBoehmStackPointer: pthread_attr_destroy failed");
88+
}
89+
# endif
90+
91+
void * osStackBase = (char *) osStackLimit + osStackSize;
92+
// NOTE: We assume the stack grows down, as it does on all architectures we support.
93+
// Architectures that grow the stack up are rare.
94+
if (sp >= osStackBase || sp < osStackLimit) { // sp is outside the os stack
95+
sp = osStackLimit;
96+
}
97+
}
98+
3899
static inline void initGCReal()
39100
{
40101
/* Initialise the Boehm garbage collector. */
@@ -62,6 +123,9 @@ static inline void initGCReal()
62123

63124
GC_set_oom_fn(oomHandler);
64125

126+
GC_set_sp_corrector(&fixupBoehmStackPointer);
127+
assert(GC_get_sp_corrector());
128+
65129
/* Set the initial heap size to something fairly big (25% of
66130
physical RAM, up to a maximum of 384 MiB) so that in most cases
67131
we don't need to garbage collect at all. (Collection has a

0 commit comments

Comments
 (0)