Skip to content

Commit ca8e99f

Browse files
committed
rt: Fix scalability problem with big stacks on 32 bit
1 parent 2dbe20a commit ca8e99f

File tree

6 files changed

+131
-6
lines changed

6 files changed

+131
-6
lines changed

src/rt/rust_sched_loop.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ rust_sched_loop::rust_sched_loop(rust_scheduler *sched, int id, bool killed) :
2929
should_exit(false),
3030
cached_c_stack(NULL),
3131
extra_c_stack(NULL),
32+
cached_big_stack(NULL),
33+
extra_big_stack(NULL),
3234
dead_task(NULL),
3335
killed(killed),
3436
pump_signal(NULL),
@@ -263,6 +265,11 @@ rust_sched_loop::run_single_turn() {
263265
destroy_exchange_stack(kernel->region(), cached_c_stack);
264266
cached_c_stack = NULL;
265267
}
268+
assert(!extra_big_stack);
269+
if (cached_big_stack) {
270+
destroy_exchange_stack(kernel->region(), cached_big_stack);
271+
cached_big_stack = NULL;
272+
}
266273

267274
sched->release_task_thread();
268275
return sched_loop_state_exit;
@@ -392,6 +399,13 @@ rust_sched_loop::prepare_c_stack(rust_task *task) {
392399
cached_c_stack = create_exchange_stack(kernel->region(),
393400
C_STACK_SIZE);
394401
}
402+
assert(!extra_big_stack);
403+
if (!cached_big_stack) {
404+
cached_big_stack = create_exchange_stack(kernel->region(),
405+
C_STACK_SIZE +
406+
(C_STACK_SIZE * 2));
407+
cached_big_stack->is_big = 1;
408+
}
395409
}
396410

397411
void
@@ -400,6 +414,10 @@ rust_sched_loop::unprepare_c_stack() {
400414
destroy_exchange_stack(kernel->region(), extra_c_stack);
401415
extra_c_stack = NULL;
402416
}
417+
if (extra_big_stack) {
418+
destroy_exchange_stack(kernel->region(), extra_big_stack);
419+
extra_big_stack = NULL;
420+
}
403421
}
404422

405423
//

src/rt/rust_sched_loop.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ struct rust_sched_loop
6767

6868
stk_seg *cached_c_stack;
6969
stk_seg *extra_c_stack;
70+
stk_seg *cached_big_stack;
71+
stk_seg *extra_big_stack;
7072

7173
rust_task_list running_tasks;
7274
rust_task_list blocked_tasks;
@@ -147,6 +149,10 @@ struct rust_sched_loop
147149
stk_seg *borrow_c_stack();
148150
void return_c_stack(stk_seg *stack);
149151

152+
// Called by tasks when they need a big stack
153+
stk_seg *borrow_big_stack();
154+
void return_big_stack(stk_seg *stack);
155+
150156
int get_id() { return this->id; }
151157
};
152158

@@ -202,6 +208,32 @@ rust_sched_loop::return_c_stack(stk_seg *stack) {
202208
}
203209
}
204210

211+
// NB: Runs on the Rust stack. Might return NULL!
212+
inline stk_seg *
213+
rust_sched_loop::borrow_big_stack() {
214+
assert(cached_big_stack);
215+
stk_seg *your_stack;
216+
if (extra_big_stack) {
217+
your_stack = extra_big_stack;
218+
extra_big_stack = NULL;
219+
} else {
220+
your_stack = cached_big_stack;
221+
cached_big_stack = NULL;
222+
}
223+
return your_stack;
224+
}
225+
226+
// NB: Runs on the Rust stack
227+
inline void
228+
rust_sched_loop::return_big_stack(stk_seg *stack) {
229+
assert(!extra_big_stack);
230+
assert(stack);
231+
if (!cached_big_stack)
232+
cached_big_stack = stack;
233+
else
234+
extra_big_stack = stack;
235+
}
236+
205237
// this is needed to appease the circular dependency gods
206238
#include "rust_task.h"
207239

src/rt/rust_stack.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "vg/valgrind.h"
1414
#include "vg/memcheck.h"
1515

16+
#include <cstdio>
17+
1618
#ifdef _LP64
1719
const uintptr_t canary_value = 0xABCDABCDABCDABCD;
1820
#else
@@ -61,6 +63,7 @@ create_stack(memory_region *region, size_t sz) {
6163
stk_seg *stk = (stk_seg *)region->malloc(total_sz, "stack");
6264
memset(stk, 0, sizeof(stk_seg));
6365
stk->end = (uintptr_t) &stk->data[sz];
66+
stk->is_big = 0;
6467
add_stack_canary(stk);
6568
register_valgrind_stack(stk);
6669
return stk;
@@ -78,6 +81,7 @@ create_exchange_stack(rust_exchange_alloc *exchange, size_t sz) {
7881
stk_seg *stk = (stk_seg *)exchange->malloc(total_sz);
7982
memset(stk, 0, sizeof(stk_seg));
8083
stk->end = (uintptr_t) &stk->data[sz];
84+
stk->is_big = 0;
8185
add_stack_canary(stk);
8286
register_valgrind_stack(stk);
8387
return stk;

src/rt/rust_stack.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ struct stk_seg {
2222
stk_seg *next;
2323
uintptr_t end;
2424
unsigned int valgrind_id;
25-
#ifndef _LP64
26-
uint32_t pad;
27-
#endif
25+
uint8_t is_big;
2826

2927
rust_task *task;
3028
uintptr_t canary;

src/rt/rust_task.cpp

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ rust_task::rust_task(rust_sched_loop *sched_loop, rust_task_state state,
5353
disallow_yield(0),
5454
c_stack(NULL),
5555
next_c_sp(0),
56-
next_rust_sp(0)
56+
next_rust_sp(0),
57+
big_stack(NULL)
5758
{
5859
LOGPTR(sched_loop, "new task", (uintptr_t)this);
5960
DLOG(sched_loop, task, "sizeof(task) = %d (0x%x)",
@@ -556,13 +557,64 @@ rust_task::cleanup_after_turn() {
556557
// Delete any spare stack segments that were left
557558
// behind by calls to prev_stack
558559
assert(stk);
560+
559561
while (stk->next) {
560562
stk_seg *new_next = stk->next->next;
561-
free_stack(stk->next);
563+
564+
if (stk->next->is_big) {
565+
assert (big_stack == stk->next);
566+
sched_loop->return_big_stack(big_stack);
567+
big_stack = NULL;
568+
} else {
569+
free_stack(stk->next);
570+
}
571+
562572
stk->next = new_next;
563573
}
564574
}
565575

576+
// NB: Runs on the Rust stack. Returns true if we successfully allocated the big
577+
// stack and false otherwise.
578+
bool
579+
rust_task::new_big_stack() {
580+
// If we have a cached big stack segment, use it.
581+
if (big_stack) {
582+
// Check to see if we're already on the big stack.
583+
stk_seg *ss = stk;
584+
while (ss != NULL) {
585+
if (ss == big_stack)
586+
return false;
587+
ss = ss->prev;
588+
}
589+
590+
// Unlink the big stack.
591+
if (big_stack->next)
592+
big_stack->next->prev = big_stack->prev;
593+
if (big_stack->prev)
594+
big_stack->prev->next = big_stack->next;
595+
} else {
596+
stk_seg *borrowed_big_stack = sched_loop->borrow_big_stack();
597+
if (!borrowed_big_stack) {
598+
dump_stacks();
599+
abort();
600+
} else {
601+
big_stack = borrowed_big_stack;
602+
}
603+
}
604+
605+
big_stack->task = this;
606+
big_stack->next = stk->next;
607+
if (big_stack->next)
608+
big_stack->next->prev = big_stack;
609+
big_stack->prev = stk;
610+
if (stk)
611+
stk->next = big_stack;
612+
613+
stk = big_stack;
614+
615+
return true;
616+
}
617+
566618
static bool
567619
sp_in_stk_seg(uintptr_t sp, stk_seg *stk) {
568620
// Not positive these bounds for sp are correct. I think that the first
@@ -602,9 +654,16 @@ rust_task::delete_all_stacks() {
602654
assert(stk->next == NULL);
603655
while (stk != NULL) {
604656
stk_seg *prev = stk->prev;
605-
free_stack(stk);
657+
658+
if (stk->is_big)
659+
sched_loop->return_big_stack(stk);
660+
else
661+
free_stack(stk);
662+
606663
stk = prev;
607664
}
665+
666+
big_stack = NULL;
608667
}
609668

610669
/*

src/rt/rust_task.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@
133133
#define RZ_BSD_32 (1024*20)
134134
#define RZ_BSD_64 (1024*20)
135135

136+
// The threshold beyond which we switch to the C stack.
137+
#define STACK_THRESHOLD (1024 * 1024)
138+
136139
#ifdef __linux__
137140
#ifdef __i386__
138141
#define RED_ZONE_SIZE RZ_LINUX_32
@@ -263,9 +266,13 @@ rust_task : public kernel_owned<rust_task>
263266
uintptr_t next_c_sp;
264267
uintptr_t next_rust_sp;
265268

269+
// The big stack.
270+
stk_seg *big_stack;
271+
266272
// Called when the atomic refcount reaches zero
267273
void delete_this();
268274

275+
bool new_big_stack();
269276
void new_stack_fast(size_t requested_sz);
270277
void new_stack(size_t requested_sz);
271278
void free_stack(stk_seg *stk);
@@ -284,6 +291,8 @@ rust_task : public kernel_owned<rust_task>
284291
char const *file,
285292
size_t line);
286293

294+
void dump_stacks();
295+
287296
friend void task_start_wrapper(spawn_args *a);
288297
friend void cleanup_task(cleanup_args *a);
289298
friend void reset_stack_limit_on_c_stack(reset_args *a);
@@ -568,6 +577,11 @@ rust_task::new_stack_fast(size_t requested_sz) {
568577
// The minimum stack size, in bytes, of a Rust stack, excluding red zone
569578
size_t min_sz = sched_loop->min_stack_size;
570579

580+
if (requested_sz > STACK_THRESHOLD) {
581+
if (new_big_stack())
582+
return;
583+
}
584+
571585
// Try to reuse an existing stack segment
572586
if (stk != NULL && stk->next != NULL) {
573587
size_t next_sz = user_stack_size(stk->next);

0 commit comments

Comments
 (0)