]> git.lizzy.rs Git - rust.git/blob - src/rt/rust_task.cpp
simplify task impl
[rust.git] / src / rt / rust_task.cpp
1
2 #include "rust_internal.h"
3 #include "rust_cc.h"
4
5 #include "vg/valgrind.h"
6 #include "vg/memcheck.h"
7
8 #ifndef __WIN32__
9 #include <execinfo.h>
10 #endif
11 #include <iostream>
12 #include <cassert>
13 #include <cstring>
14 #include <algorithm>
15
16 #include "globals.h"
17
18 // The amount of extra space at the end of each stack segment, available
19 // to the rt, compiler and dynamic linker for running small functions
20 // FIXME: We want this to be 128 but need to slim the red zone calls down
21 #define RZ_LINUX_32 (1024*2)
22 #define RZ_LINUX_64 (1024*2)
23 #define RZ_MAC_32   (1024*20)
24 #define RZ_MAC_64   (1024*20)
25 #define RZ_WIN_32   (1024*20)
26 #define RZ_BSD_32   (1024*20)
27 #define RZ_BSD_64   (1024*20)
28
29 #ifdef __linux__
30 #ifdef __i386__
31 #define RED_ZONE_SIZE RZ_LINUX_32
32 #endif
33 #ifdef __x86_64__
34 #define RED_ZONE_SIZE RZ_LINUX_64
35 #endif
36 #endif
37 #ifdef __APPLE__
38 #ifdef __i386__
39 #define RED_ZONE_SIZE RZ_MAC_32
40 #endif
41 #ifdef __x86_64__
42 #define RED_ZONE_SIZE RZ_MAC_64
43 #endif
44 #endif
45 #ifdef __WIN32__
46 #ifdef __i386__
47 #define RED_ZONE_SIZE RZ_WIN_32
48 #endif
49 #ifdef __x86_64__
50 #define RED_ZONE_SIZE RZ_WIN_64
51 #endif
52 #endif
53 #ifdef __FreeBSD__
54 #ifdef __i386__
55 #define RED_ZONE_SIZE RZ_BSD_32
56 #endif
57 #ifdef __x86_64__
58 #define RED_ZONE_SIZE RZ_BSD_64
59 #endif
60 #endif
61
62 // A value that goes at the end of the stack and must not be touched
63 const uint8_t stack_canary[] = {0xAB, 0xCD, 0xAB, 0xCD,
64                                 0xAB, 0xCD, 0xAB, 0xCD,
65                                 0xAB, 0xCD, 0xAB, 0xCD,
66                                 0xAB, 0xCD, 0xAB, 0xCD};
67
68 // Stack size
69 size_t g_custom_min_stack_size = 0;
70
71 static size_t
72 get_min_stk_size(size_t default_size) {
73     if (g_custom_min_stack_size != 0) {
74         return g_custom_min_stack_size;
75     } else {
76         return default_size;
77     }
78 }
79
80 static size_t
81 get_next_stk_size(rust_scheduler *sched, rust_task *task,
82                   size_t min, size_t current, size_t requested) {
83     LOG(task, mem, "calculating new stack size for 0x%" PRIxPTR, task);
84     LOG(task, mem,
85         "min: %" PRIdPTR " current: %" PRIdPTR " requested: %" PRIdPTR,
86         min, current, requested);
87
88     // Allocate at least enough to accomodate the next frame
89     size_t sz = std::max(min, requested);
90
91     // And double the stack size each allocation
92     const size_t max = 1024 * 1024;
93     size_t next = std::min(max, current * 2);
94
95     sz = std::max(sz, next);
96
97     LOG(task, mem, "next stack size: %" PRIdPTR, sz);
98     I(sched, requested <= sz);
99     return sz;
100 }
101
102 // Task stack segments. Heap allocated and chained together.
103
104 static void
105 config_valgrind_stack(stk_seg *stk) {
106     stk->valgrind_id =
107         VALGRIND_STACK_REGISTER(&stk->data[0],
108                                 stk->end);
109 #ifndef NVALGRIND
110     // Establish that the stack is accessible.  This must be done when reusing
111     // old stack segments, since the act of popping the stack previously
112     // caused valgrind to consider the whole thing inaccessible.
113     size_t sz = stk->end - (uintptr_t)&stk->data[0];
114     VALGRIND_MAKE_MEM_UNDEFINED(stk->data + sizeof(stack_canary),
115                                 sz - sizeof(stack_canary));
116 #endif
117 }
118
119 static void
120 unconfig_valgrind_stack(stk_seg *stk) {
121 VALGRIND_STACK_DEREGISTER(stk->valgrind_id);
122 }
123
124 static void
125 free_stk(rust_task *task, stk_seg *stk) {
126     LOGPTR(task->sched, "freeing stk segment", (uintptr_t)stk);
127     task->free(stk);
128 }
129
130 static void
131 add_stack_canary(stk_seg *stk) {
132     memcpy(stk->data, stack_canary, sizeof(stack_canary));
133     assert(sizeof(stack_canary) == 16 && "Stack canary was not the expected size");
134 }
135
136 static void
137 check_stack_canary(stk_seg *stk) {
138     assert(!memcmp(stk->data, stack_canary, sizeof(stack_canary))
139       && "Somebody killed the canary");
140 }
141
142 static stk_seg*
143 new_stk(rust_scheduler *sched, rust_task *task, size_t requested_sz)
144 {
145     LOG(task, mem, "creating new stack for task %" PRIxPTR, task);
146     if (task->stk) {
147         check_stack_canary(task->stk);
148     }
149
150     // The minimum stack size, in bytes, of a Rust stack, excluding red zone
151     size_t min_sz = get_min_stk_size(sched->min_stack_size);
152
153     // Try to reuse an existing stack segment
154     if (task->stk != NULL && task->stk->prev != NULL) {
155         size_t prev_sz = (size_t)(task->stk->prev->end
156                                   - (uintptr_t)&task->stk->prev->data[0]
157                                   - RED_ZONE_SIZE);
158         if (min_sz <= prev_sz && requested_sz <= prev_sz) {
159             LOG(task, mem, "reusing existing stack");
160             task->stk = task->stk->prev;
161             A(sched, task->stk->prev == NULL, "Bogus stack ptr");
162             config_valgrind_stack(task->stk);
163             return task->stk;
164         } else {
165             LOG(task, mem, "existing stack is not big enough");
166             free_stk(task, task->stk->prev);
167             task->stk->prev = NULL;
168         }
169     }
170
171     // The size of the current stack segment, excluding red zone
172     size_t current_sz = 0;
173     if (task->stk != NULL) {
174         current_sz = (size_t)(task->stk->end
175                               - (uintptr_t)&task->stk->data[0]
176                               - RED_ZONE_SIZE);
177     }
178     // The calculated size of the new stack, excluding red zone
179     size_t rust_stk_sz = get_next_stk_size(sched, task, min_sz,
180                                            current_sz, requested_sz);
181
182     size_t sz = sizeof(stk_seg) + rust_stk_sz + RED_ZONE_SIZE;
183     stk_seg *stk = (stk_seg *)task->malloc(sz, "stack");
184     LOGPTR(task->sched, "new stk", (uintptr_t)stk);
185     memset(stk, 0, sizeof(stk_seg));
186     add_stack_canary(stk);
187     stk->prev = NULL;
188     stk->next = task->stk;
189     stk->end = (uintptr_t) &stk->data[rust_stk_sz + RED_ZONE_SIZE];
190     LOGPTR(task->sched, "stk end", stk->end);
191
192     task->stk = stk;
193     config_valgrind_stack(task->stk);
194     return stk;
195 }
196
197 static void
198 del_stk(rust_task *task, stk_seg *stk)
199 {
200     assert(stk == task->stk && "Freeing stack segments out of order!");
201     check_stack_canary(stk);
202
203     task->stk = stk->next;
204
205     bool delete_stack = false;
206     if (task->stk != NULL) {
207         // Don't actually delete this stack. Save it to reuse later,
208         // preventing the pathological case where we repeatedly reallocate
209         // the stack for the next frame.
210         task->stk->prev = stk;
211     } else {
212         // This is the last stack, delete it.
213         delete_stack = true;
214     }
215
216     // Delete the previous previous stack
217     if (stk->prev != NULL) {
218         free_stk(task, stk->prev);
219         stk->prev = NULL;
220     }
221
222     unconfig_valgrind_stack(stk);
223     if (delete_stack) {
224         free_stk(task, stk);
225     }
226 }
227
228 // Tasks
229 rust_task::rust_task(rust_scheduler *sched, rust_task_list *state,
230                      rust_task *spawner, const char *name) :
231     ref_count(1),
232     stk(NULL),
233     runtime_sp(0),
234     sched(sched),
235     cache(NULL),
236     kernel(sched->kernel),
237     name(name),
238     state(state),
239     cond(NULL),
240     cond_name("none"),
241     supervisor(spawner),
242     list_index(-1),
243     next_port_id(0),
244     rendezvous_ptr(0),
245     running_on(-1),
246     pinned_on(-1),
247     local_region(&sched->srv->local_region),
248     failed(false),
249     killed(false),
250     propagate_failure(true),
251     dynastack(this),
252     cc_counter(0)
253 {
254     LOGPTR(sched, "new task", (uintptr_t)this);
255     DLOG(sched, task, "sizeof(task) = %d (0x%x)", sizeof *this, sizeof *this);
256
257     assert((void*)this == (void*)&user);
258
259     user.notify_enabled = 0;
260
261     stk = new_stk(sched, this, 0);
262     user.rust_sp = stk->end;
263     if (supervisor) {
264         supervisor->ref();
265     }
266 }
267
268 rust_task::~rust_task()
269 {
270     I(sched, !sched->lock.lock_held_by_current_thread());
271     I(sched, port_table.is_empty());
272     DLOG(sched, task, "~rust_task %s @0x%" PRIxPTR ", refcnt=%d",
273          name, (uintptr_t)this, ref_count);
274
275     if (supervisor) {
276         supervisor->deref();
277     }
278
279     kernel->release_task_id(user.id);
280
281     /* FIXME: tighten this up, there are some more
282        assertions that hold at task-lifecycle events. */
283     I(sched, ref_count == 0); // ||
284     //   (ref_count == 1 && this == sched->root_task));
285
286     // Delete all the stacks. There may be more than one if the task failed
287     // and no landing pads stopped to clean up.
288     while (stk != NULL) {
289         del_stk(this, stk);
290     }
291 }
292
293 struct spawn_args {
294     rust_task *task;
295     uintptr_t envptr;
296     spawn_fn f;
297 };
298
299 struct rust_closure {
300     const type_desc *td;
301     // ... see trans_closure.rs for full description ...
302 };
303
304 struct rust_boxed_closure {
305     intptr_t ref_count;
306     rust_closure closure;
307 };
308
309 struct cleanup_args {
310     spawn_args *spargs;
311     bool failed;
312 };
313
314 void
315 cleanup_task(cleanup_args *args) {
316     spawn_args *a = args->spargs;
317     bool failed = args->failed;
318     rust_task *task = a->task;
319
320     cc::do_cc(task);
321
322     rust_boxed_closure* boxed_env = (rust_boxed_closure*)a->envptr;
323     if(boxed_env) {
324         // free the environment.
325         rust_closure *env = &boxed_env->closure;
326         env->td->drop_glue(NULL, NULL, &env->td, env);
327         env->td->free_glue(NULL, NULL, &env->td, env);
328     }
329
330     task->die();
331
332     if (task->killed && !failed) {
333         LOG(task, task, "Task killed during termination");
334         failed = true;
335     }
336
337     task->notify(!failed);
338
339     if (failed) {
340 #ifndef __WIN32__
341         task->conclude_failure();
342 #else
343         A(task->sched, false, "Shouldn't happen");
344 #endif
345     }
346 }
347
348 // This runs on the Rust stack
349 extern "C" CDECL
350 void task_start_wrapper(spawn_args *a)
351 {
352     rust_task *task = a->task;
353
354     bool failed = false;
355     try {
356         // The first argument is the return pointer; as the task fn 
357         // must have void return type, we can safely pass 0.
358         a->f(0, a->envptr);
359     } catch (rust_task *ex) {
360         A(task->sched, ex == task,
361           "Expected this task to be thrown for unwinding");
362         failed = true;
363     }
364
365     cleanup_args ca = {a, failed};
366
367     // The cleanup work needs lots of stack
368     task->sched->c_context.call_shim_on_c_stack(&ca, (void*)cleanup_task);
369
370     task->ctx.next->swap(task->ctx);
371 }
372
373 void
374 rust_task::start(spawn_fn spawnee_fn,
375                  uintptr_t env)
376 {
377     LOG(this, task, "starting task from fn 0x%" PRIxPTR
378         " with env 0x%" PRIxPTR, spawnee_fn, env);
379
380     I(sched, stk->data != NULL);
381
382     char *sp = (char *)user.rust_sp;
383
384     sp -= sizeof(spawn_args);
385
386     spawn_args *a = (spawn_args *)sp;
387
388     a->task = this;
389     a->envptr = env;
390     a->f = spawnee_fn;
391
392     ctx.call((void *)task_start_wrapper, a, sp);
393
394     this->start();
395 }
396
397 void rust_task::start()
398 {
399     yield_timer.reset_us(0);
400     transition(&sched->newborn_tasks, &sched->running_tasks);
401     sched->lock.signal();
402 }
403
404 // Only run this on the rust stack
405 void
406 rust_task::yield(size_t time_in_us, bool *killed) {
407     if (this->killed) {
408         *killed = true;
409     }
410
411     yield_timer.reset_us(time_in_us);
412
413     // Return to the scheduler.
414     ctx.next->swap(ctx);
415
416     if (this->killed) {
417         *killed = true;
418     }
419 }
420
421 void
422 rust_task::kill() {
423     if (dead()) {
424         // Task is already dead, can't kill what's already dead.
425         fail_parent();
426         return;
427     }
428
429     // Note the distinction here: kill() is when you're in an upcall
430     // from task A and want to force-fail task B, you do B->kill().
431     // If you want to fail yourself you do self->fail().
432     LOG(this, task, "killing task %s @0x%" PRIxPTR, name, this);
433     // When the task next goes to yield or resume it will fail
434     killed = true;
435     // Unblock the task so it can unwind.
436     unblock();
437
438     sched->lock.signal();
439
440     LOG(this, task, "preparing to unwind task: 0x%" PRIxPTR, this);
441     // run_on_resume(rust_unwind_glue);
442 }
443
444 void
445 rust_task::fail() {
446     // See note in ::kill() regarding who should call this.
447     DLOG(sched, task, "task %s @0x%" PRIxPTR " failing", name, this);
448     backtrace();
449 #ifndef __WIN32__
450     throw this;
451 #else
452     die();
453     conclude_failure();
454     // FIXME: Need unwinding on windows. This will end up aborting
455     sched->fail();
456 #endif
457 }
458
459 void
460 rust_task::conclude_failure() {
461     fail_parent();
462     failed = true;
463 }
464
465 void
466 rust_task::fail_parent() {
467     if (supervisor) {
468         DLOG(sched, task,
469              "task %s @0x%" PRIxPTR
470              " propagating failure to supervisor %s @0x%" PRIxPTR,
471              name, this, supervisor->name, supervisor);
472         supervisor->kill();
473     }
474     // FIXME: implement unwinding again.
475     if (NULL == supervisor && propagate_failure)
476         sched->fail();
477 }
478
479 void
480 rust_task::unsupervise()
481 {
482     DLOG(sched, task,
483              "task %s @0x%" PRIxPTR
484              " disconnecting from supervisor %s @0x%" PRIxPTR,
485              name, this, supervisor->name, supervisor);
486     if (supervisor) {
487         supervisor->deref();
488     }
489     supervisor = NULL;
490     propagate_failure = false;
491 }
492
493 frame_glue_fns*
494 rust_task::get_frame_glue_fns(uintptr_t fp) {
495     fp -= sizeof(uintptr_t);
496     return *((frame_glue_fns**) fp);
497 }
498
499 bool
500 rust_task::running()
501 {
502     return state == &sched->running_tasks;
503 }
504
505 bool
506 rust_task::blocked()
507 {
508     return state == &sched->blocked_tasks;
509 }
510
511 bool
512 rust_task::blocked_on(rust_cond *on)
513 {
514     return blocked() && cond == on;
515 }
516
517 bool
518 rust_task::dead()
519 {
520     return state == &sched->dead_tasks;
521 }
522
523 void *
524 rust_task::malloc(size_t sz, const char *tag, type_desc *td)
525 {
526     return local_region.malloc(sz, tag);
527 }
528
529 void *
530 rust_task::realloc(void *data, size_t sz, bool is_gc)
531 {
532     return local_region.realloc(data, sz);
533 }
534
535 void
536 rust_task::free(void *p, bool is_gc)
537 {
538     local_region.free(p);
539 }
540
541 void
542 rust_task::transition(rust_task_list *src, rust_task_list *dst) {
543     bool unlock = false;
544     if(!sched->lock.lock_held_by_current_thread()) {
545         unlock = true;
546         sched->lock.lock();
547     }
548     DLOG(sched, task,
549          "task %s " PTR " state change '%s' -> '%s' while in '%s'",
550          name, (uintptr_t)this, src->name, dst->name, state->name);
551     I(sched, state == src);
552     src->remove(this);
553     dst->append(this);
554     state = dst;
555     if(unlock)
556         sched->lock.unlock();
557 }
558
559 void
560 rust_task::block(rust_cond *on, const char* name) {
561     I(sched, !lock.lock_held_by_current_thread());
562     scoped_lock with(lock);
563     LOG(this, task, "Blocking on 0x%" PRIxPTR ", cond: 0x%" PRIxPTR,
564                          (uintptr_t) on, (uintptr_t) cond);
565     A(sched, cond == NULL, "Cannot block an already blocked task.");
566     A(sched, on != NULL, "Cannot block on a NULL object.");
567
568     transition(&sched->running_tasks, &sched->blocked_tasks);
569     cond = on;
570     cond_name = name;
571 }
572
573 void
574 rust_task::wakeup(rust_cond *from) {
575     I(sched, !lock.lock_held_by_current_thread());
576     scoped_lock with(lock);
577     A(sched, cond != NULL, "Cannot wake up unblocked task.");
578     LOG(this, task, "Blocked on 0x%" PRIxPTR " woken up on 0x%" PRIxPTR,
579                         (uintptr_t) cond, (uintptr_t) from);
580     A(sched, cond == from, "Cannot wake up blocked task on wrong condition.");
581
582     transition(&sched->blocked_tasks, &sched->running_tasks);
583     I(sched, cond == from);
584     cond = NULL;
585     cond_name = "none";
586
587     sched->lock.signal();
588 }
589
590 void
591 rust_task::die() {
592     I(sched, !lock.lock_held_by_current_thread());
593     scoped_lock with(lock);
594     transition(&sched->running_tasks, &sched->dead_tasks);
595     sched->lock.signal();
596 }
597
598 void
599 rust_task::unblock() {
600     if (blocked()) {
601         // FIXME: What if another thread unblocks the task between when
602         // we checked and here?
603         wakeup(cond);
604     }
605 }
606
607 rust_crate_cache *
608 rust_task::get_crate_cache()
609 {
610     if (!cache) {
611         DLOG(sched, task, "fetching cache for current crate");
612         cache = sched->get_cache();
613     }
614     return cache;
615 }
616
617 void
618 rust_task::backtrace() {
619     if (!log_rt_backtrace) return;
620 #ifndef __WIN32__
621     void *call_stack[256];
622     int nframes = ::backtrace(call_stack, 256);
623     backtrace_symbols_fd(call_stack + 1, nframes - 1, 2);
624 #endif
625 }
626
627 bool rust_task::can_schedule(int id)
628 {
629     return yield_timer.has_timed_out() &&
630         running_on == -1 &&
631         (pinned_on == -1 || pinned_on == id);
632 }
633
634 void *
635 rust_task::calloc(size_t size, const char *tag) {
636     return local_region.calloc(size, tag);
637 }
638
639 void rust_task::pin() {
640     I(this->sched, running_on != -1);
641     pinned_on = running_on;
642 }
643
644 void rust_task::pin(int id) {
645     I(this->sched, running_on == -1);
646     pinned_on = id;
647 }
648
649 void rust_task::unpin() {
650     pinned_on = -1;
651 }
652
653 rust_port_id rust_task::register_port(rust_port *port) {
654     I(sched, !lock.lock_held_by_current_thread());
655     scoped_lock with(lock);
656
657     rust_port_id id = next_port_id++;
658     port_table.put(id, port);
659     return id;
660 }
661
662 void rust_task::release_port(rust_port_id id) {
663     I(sched, lock.lock_held_by_current_thread());
664     port_table.remove(id);
665 }
666
667 rust_port *rust_task::get_port_by_id(rust_port_id id) {
668     I(sched, !lock.lock_held_by_current_thread());
669     scoped_lock with(lock);
670     rust_port *port = NULL;
671     port_table.get(id, &port);
672     if (port) {
673         port->ref();
674     }
675     return port;
676 }
677
678
679 // Temporary routine to allow boxes on one task's shared heap to be reparented
680 // to another.
681 const type_desc *
682 rust_task::release_alloc(void *alloc) {
683     I(sched, !lock.lock_held_by_current_thread());
684     lock.lock();
685
686     assert(local_allocs.find(alloc) != local_allocs.end());
687     const type_desc *tydesc = local_allocs[alloc];
688     local_allocs.erase(alloc);
689
690     local_region.release_alloc(alloc);
691
692     lock.unlock();
693     return tydesc;
694 }
695
696 // Temporary routine to allow boxes from one task's shared heap to be
697 // reparented to this one.
698 void
699 rust_task::claim_alloc(void *alloc, const type_desc *tydesc) {
700     I(sched, !lock.lock_held_by_current_thread());
701     lock.lock();
702
703     assert(local_allocs.find(alloc) == local_allocs.end());
704     local_allocs[alloc] = tydesc;
705     local_region.claim_alloc(alloc);
706
707     lock.unlock();
708 }
709
710 void
711 rust_task::notify(bool success) {
712     // FIXME (1078) Do this in rust code
713     if(user.notify_enabled) {
714         rust_task *target_task = kernel->get_task_by_id(user.notify_chan.task);
715         if (target_task) {
716             rust_port *target_port =
717                 target_task->get_port_by_id(user.notify_chan.port);
718             if(target_port) {
719                 task_notification msg;
720                 msg.id = user.id;
721                 msg.result = !success ? tr_failure : tr_success;
722
723                 target_port->send(&msg);
724                 scoped_lock with(target_task->lock);
725                 target_port->deref();
726             }
727             target_task->deref();
728         }
729     }
730 }
731
732 extern "C" CDECL void
733 record_sp(void *limit);
734
735 void *
736 rust_task::new_stack(size_t stk_sz, void *args_addr, size_t args_sz) {
737
738     stk_seg *stk_seg = new_stk(sched, this, stk_sz + args_sz);
739     A(sched, stk_seg->end - (uintptr_t)stk_seg->data >= stk_sz + args_sz,
740       "Did not receive enough stack");
741     uint8_t *new_sp = (uint8_t*)stk_seg->end;
742     // Push the function arguments to the new stack
743     new_sp = align_down(new_sp - args_sz);
744     memcpy(new_sp, args_addr, args_sz);
745     record_stack_limit();
746     return new_sp;
747 }
748
749 void
750 rust_task::del_stack() {
751     del_stk(this, stk);
752     record_stack_limit();
753 }
754
755 void
756 rust_task::record_stack_limit() {
757     // The function prolog compares the amount of stack needed to the end of
758     // the stack. As an optimization, when the frame size is less than 256
759     // bytes, it will simply compare %esp to to the stack limit instead of
760     // subtracting the frame size. As a result we need our stack limit to
761     // account for those 256 bytes.
762     const unsigned LIMIT_OFFSET = 256;
763     A(sched,
764       (uintptr_t)stk->end - RED_ZONE_SIZE
765       - (uintptr_t)stk->data >= LIMIT_OFFSET,
766       "Stack size must be greater than LIMIT_OFFSET");
767     record_sp(stk->data + LIMIT_OFFSET + RED_ZONE_SIZE);
768 }
769
770 extern "C" uintptr_t get_sp();
771
772 static bool
773 sp_in_stk_seg(uintptr_t sp, stk_seg *stk) {
774     // Not positive these bounds for sp are correct.  I think that the first
775     // possible value for esp on a new stack is stk->end, which points to the
776     // address before the first value to be pushed onto a new stack. The last
777     // possible address we can push data to is stk->data.  Regardless, there's
778     // so much slop at either end that we should never hit one of these
779     // boundaries.
780     return (uintptr_t)stk->data <= sp && sp <= stk->end;
781 }
782
783 /*
784 Called by landing pads during unwinding to figure out which
785 stack segment we are currently running on, delete the others,
786 and record the stack limit (which was not restored when unwinding
787 through __morestack).
788  */
789 void
790 rust_task::reset_stack_limit() {
791     uintptr_t sp = get_sp();
792     while (!sp_in_stk_seg(sp, stk)) {
793         del_stk(this, stk);
794         A(sched, stk != NULL, "Failed to find the current stack");
795     }
796     record_stack_limit();
797 }
798
799 /*
800 Returns true if we're currently running on the Rust stack
801  */
802 bool
803 rust_task::on_rust_stack() {
804     return sp_in_stk_seg(get_sp(), stk);
805 }
806
807 void
808 rust_task::check_stack_canary() {
809     ::check_stack_canary(stk);
810 }
811
812 //
813 // Local Variables:
814 // mode: C++
815 // fill-column: 78;
816 // indent-tabs-mode: nil
817 // c-basic-offset: 4
818 // buffer-file-coding-system: utf-8-unix
819 // End:
820 //