]> git.lizzy.rs Git - rust.git/blob - src/rt/rust_task.cpp
Populate tree.
[rust.git] / src / rt / rust_task.cpp
1
2 #include "rust_internal.h"
3
4 #include "valgrind.h"
5 #include "memcheck.h"
6
7 // Stacks
8
9 static size_t const min_stk_bytes = 0x300;
10
11 // Task stack segments. Heap allocated and chained together.
12
13 static stk_seg*
14 new_stk(rust_dom *dom, size_t minsz)
15 {
16     if (minsz < min_stk_bytes)
17         minsz = min_stk_bytes;
18     size_t sz = sizeof(stk_seg) + minsz;
19     stk_seg *stk = (stk_seg *)dom->malloc(sz);
20     dom->logptr("new stk", (uintptr_t)stk);
21     memset(stk, 0, sizeof(stk_seg));
22     stk->limit = (uintptr_t) &stk->data[minsz];
23     dom->logptr("stk limit", stk->limit);
24     stk->valgrind_id =
25         VALGRIND_STACK_REGISTER(&stk->data[0],
26                                 &stk->data[minsz]);
27     return stk;
28 }
29
30 static void
31 del_stk(rust_dom *dom, stk_seg *stk)
32 {
33     VALGRIND_STACK_DEREGISTER(stk->valgrind_id);
34     dom->logptr("freeing stk segment", (uintptr_t)stk);
35     dom->free(stk);
36 }
37
38 // Tasks
39
40 // FIXME (issue #31): ifdef by platform. This is getting absurdly
41 // x86-specific.
42
43 size_t const n_callee_saves = 4;
44 size_t const callee_save_fp = 0;
45
46 static uintptr_t
47 align_down(uintptr_t sp)
48 {
49     // There is no platform we care about that needs more than a
50     // 16-byte alignment.
51     return sp & ~(16 - 1);
52 }
53
54
55 rust_task::rust_task(rust_dom *dom, rust_task *spawner) :
56     stk(new_stk(dom, 0)),
57     runtime_sp(0),
58     rust_sp(stk->limit),
59     gc_alloc_chain(0),
60     dom(dom),
61     cache(NULL),
62     state(&dom->running_tasks),
63     cond(NULL),
64     dptr(0),
65     spawner(spawner),
66     idx(0),
67     waiting_tasks(dom),
68     alarm(this)
69 {
70     dom->logptr("new task", (uintptr_t)this);
71 }
72
73 rust_task::~rust_task()
74 {
75     dom->log(rust_log::MEM|rust_log::TASK,
76              "~rust_task 0x%" PRIxPTR ", refcnt=%d",
77              (uintptr_t)this, refcnt);
78
79     /*
80       for (uintptr_t fp = get_fp(); fp; fp = get_previous_fp(fp)) {
81       frame_glue_fns *glue_fns = get_frame_glue_fns(fp);
82       dom->log(rust_log::MEM|rust_log::TASK,
83       "~rust_task, frame fp=0x%" PRIxPTR ", glue_fns=0x%" PRIxPTR,
84       fp, glue_fns);
85       if (glue_fns) {
86       dom->log(rust_log::MEM|rust_log::TASK,
87                "~rust_task, mark_glue=0x%" PRIxPTR,
88                glue_fns->mark_glue);
89       dom->log(rust_log::MEM|rust_log::TASK,
90                "~rust_task, drop_glue=0x%" PRIxPTR,
91                glue_fns->drop_glue);
92       dom->log(rust_log::MEM|rust_log::TASK,
93                "~rust_task, reloc_glue=0x%" PRIxPTR,
94                glue_fns->reloc_glue);
95       }
96       }
97     */
98
99     /* FIXME: tighten this up, there are some more
100        assertions that hold at task-lifecycle events. */
101     I(dom, refcnt == 0 ||
102       (refcnt == 1 && this == dom->root_task));
103
104     del_stk(dom, stk);
105     if (cache)
106         cache->deref();
107 }
108
109 void
110 rust_task::start(uintptr_t exit_task_glue,
111                  uintptr_t spawnee_fn,
112                  uintptr_t args,
113                  size_t callsz)
114 {
115     dom->logptr("exit-task glue", exit_task_glue);
116     dom->logptr("from spawnee", spawnee_fn);
117
118     // Set sp to last uintptr_t-sized cell of segment and align down.
119     rust_sp -= sizeof(uintptr_t);
120     rust_sp = align_down(rust_sp);
121
122     // Begin synthesizing frames. There are two: a "fully formed"
123     // exit-task frame at the top of the stack -- that pretends to be
124     // mid-execution -- and a just-starting frame beneath it that
125     // starts executing the first instruction of the spawnee. The
126     // spawnee *thinks* it was called by the exit-task frame above
127     // it. It wasn't; we put that fake frame in place here, but the
128     // illusion is enough for the spawnee to return to the exit-task
129     // frame when it's done, and exit.
130     uintptr_t *spp = (uintptr_t *)rust_sp;
131
132     // The exit_task_glue frame we synthesize above the frame we activate:
133     *spp-- = (uintptr_t) this;       // task
134     *spp-- = (uintptr_t) 0;          // output
135     *spp-- = (uintptr_t) 0;          // retpc
136     for (size_t j = 0; j < n_callee_saves; ++j) {
137         *spp-- = 0;
138     }
139
140     // We want 'frame_base' to point to the last callee-save in this
141     // (exit-task) frame, because we're going to inject this
142     // frame-pointer into the callee-save frame pointer value in the
143     // *next* (spawnee) frame. A cheap trick, but this means the
144     // spawnee frame will restore the proper frame pointer of the glue
145     // frame as it runs its epilogue.
146     uintptr_t frame_base = (uintptr_t) (spp+1);
147
148     *spp-- = (uintptr_t) dom->root_crate;  // crate ptr
149     *spp-- = (uintptr_t) 0;                // frame_glue_fns
150
151     // Copy args from spawner to spawnee.
152     if (args)  {
153         uintptr_t *src = (uintptr_t *)args;
154         src += 1;                  // spawn-call output slot
155         src += 1;                  // spawn-call task slot
156         // Memcpy all but the task and output pointers
157         callsz -= (2 * sizeof(uintptr_t));
158         spp = (uintptr_t*) (((uintptr_t)spp) - callsz);
159         memcpy(spp, src, callsz);
160
161         // Move sp down to point to task cell.
162         spp--;
163     } else {
164         // We're at root, starting up.
165         I(dom, callsz==0);
166     }
167
168     // The *implicit* incoming args to the spawnee frame we're
169     // activating:
170
171     *spp-- = (uintptr_t) this;            // task
172     *spp-- = (uintptr_t) 0;               // output addr
173     *spp-- = (uintptr_t) exit_task_glue;  // retpc
174
175     // The context the activate_glue needs to switch stack.
176     *spp-- = (uintptr_t) spawnee_fn;      // instruction to start at
177     for (size_t j = 0; j < n_callee_saves; ++j) {
178         // callee-saves to carry in when we activate
179         if (j == callee_save_fp)
180             *spp-- = frame_base;
181         else
182             *spp-- = NULL;
183     }
184
185     // Back up one, we overshot where sp should be.
186     rust_sp = (uintptr_t) (spp+1);
187
188     dom->add_task_to_state_vec(&dom->running_tasks, this);
189 }
190
191 void
192 rust_task::grow(size_t n_frame_bytes)
193 {
194     stk_seg *old_stk = this->stk;
195     uintptr_t old_top = (uintptr_t) old_stk->limit;
196     uintptr_t old_bottom = (uintptr_t) &old_stk->data[0];
197     uintptr_t rust_sp_disp = old_top - this->rust_sp;
198     size_t ssz = old_top - old_bottom;
199     dom->log(rust_log::MEM|rust_log::TASK|rust_log::UPCALL,
200              "upcall_grow_task(%" PRIdPTR
201              "), old size %" PRIdPTR
202              " bytes (old lim: 0x%" PRIxPTR ")",
203              n_frame_bytes, ssz, old_top);
204     ssz *= 2;
205     if (ssz < n_frame_bytes)
206         ssz = n_frame_bytes;
207     ssz = next_power_of_two(ssz);
208
209     dom->log(rust_log::MEM|rust_log::TASK, "upcall_grow_task growing stk 0x%"
210              PRIxPTR " to %d bytes", old_stk, ssz);
211
212     stk_seg *nstk = new_stk(dom, ssz);
213     uintptr_t new_top = (uintptr_t) &nstk->data[ssz];
214     size_t n_copy = old_top - old_bottom;
215     dom->log(rust_log::MEM|rust_log::TASK,
216              "copying %d bytes of stack from [0x%" PRIxPTR ", 0x%" PRIxPTR "]"
217              " to [0x%" PRIxPTR ", 0x%" PRIxPTR "]",
218              n_copy,
219              old_bottom, old_bottom + n_copy,
220              new_top - n_copy, new_top);
221
222     VALGRIND_MAKE_MEM_DEFINED((void*)old_bottom, n_copy);
223     memcpy((void*)(new_top - n_copy), (void*)old_bottom, n_copy);
224
225     nstk->limit = new_top;
226     this->stk = nstk;
227     this->rust_sp = new_top - rust_sp_disp;
228
229     dom->log(rust_log::MEM|rust_log::TASK, "processing relocations");
230
231     // FIXME (issue #32): this is the most ridiculously crude
232     // relocation scheme ever. Try actually, you know, writing out
233     // reloc descriptors?
234     size_t n_relocs = 0;
235     for (uintptr_t* p = (uintptr_t*)(new_top - n_copy);
236          p < (uintptr_t*)new_top; ++p) {
237         if (old_bottom <= *p && *p < old_top) {
238             //dom->log(rust_log::MEM, "relocating pointer 0x%" PRIxPTR
239             //        " by %d bytes", *p, (new_top - old_top));
240             n_relocs++;
241             *p += (new_top - old_top);
242         }
243     }
244     dom->log(rust_log::MEM|rust_log::TASK,
245              "processed %d relocations", n_relocs);
246     del_stk(dom, old_stk);
247     dom->logptr("grown stk limit", new_top);
248 }
249
250 void
251 push_onto_thread_stack(uintptr_t &sp, uintptr_t value)
252 {
253     asm("xchgl %0, %%esp\n"
254         "push %2\n"
255         "xchgl %0, %%esp\n"
256         : "=r" (sp)
257         : "0" (sp), "r" (value)
258         : "eax");
259 }
260
261 void
262 rust_task::run_after_return(size_t nargs, uintptr_t glue)
263 {
264     // This is only safe to call if we're the currently-running task.
265     check_active();
266
267     uintptr_t sp = runtime_sp;
268
269     // The compiler reserves nargs + 1 word for oldsp on the stack and
270     // then aligns it.
271     sp = align_down(sp - nargs * sizeof(uintptr_t));
272
273     uintptr_t *retpc = ((uintptr_t *) sp) - 1;
274     dom->log(rust_log::TASK|rust_log::MEM,
275              "run_after_return: overwriting retpc=0x%" PRIxPTR
276              " @ runtime_sp=0x%" PRIxPTR
277              " with glue=0x%" PRIxPTR,
278              *retpc, sp, glue);
279
280     // Move the current return address (which points into rust code)
281     // onto the rust stack and pretend we just called into the glue.
282     push_onto_thread_stack(rust_sp, *retpc);
283     *retpc = glue;
284 }
285
286 void
287 rust_task::run_on_resume(uintptr_t glue)
288 {
289     // This is only safe to call if we're suspended.
290     check_suspended();
291
292     // Inject glue as resume address in the suspended frame.
293     uintptr_t* rsp = (uintptr_t*) rust_sp;
294     rsp += n_callee_saves;
295     dom->log(rust_log::TASK|rust_log::MEM,
296              "run_on_resume: overwriting retpc=0x%" PRIxPTR
297              " @ rust_sp=0x%" PRIxPTR
298              " with glue=0x%" PRIxPTR,
299              *rsp, rsp, glue);
300     *rsp = glue;
301 }
302
303 void
304 rust_task::yield(size_t nargs)
305 {
306     dom->log(rust_log::TASK,
307              "task 0x%" PRIxPTR " yielding", this);
308     run_after_return(nargs, dom->root_crate->get_yield_glue());
309 }
310
311 static inline uintptr_t
312 get_callee_save_fp(uintptr_t *top_of_callee_saves)
313 {
314     return top_of_callee_saves[n_callee_saves - (callee_save_fp + 1)];
315 }
316
317 void
318 rust_task::kill() {
319     // Note the distinction here: kill() is when you're in an upcall
320     // from task A and want to force-fail task B, you do B->kill().
321     // If you want to fail yourself you do self->fail(upcall_nargs).
322     dom->log(rust_log::TASK, "killing task 0x%" PRIxPTR, this);
323     // Unblock the task so it can unwind.
324     unblock();
325     if (this == dom->root_task)
326         dom->fail();
327     run_on_resume(dom->root_crate->get_unwind_glue());
328 }
329
330 void
331 rust_task::fail(size_t nargs) {
332     // See note in ::kill() regarding who should call this.
333     dom->log(rust_log::TASK, "task 0x%" PRIxPTR " failing", this);
334     // Unblock the task so it can unwind.
335     unblock();
336     if (this == dom->root_task)
337         dom->fail();
338     run_after_return(nargs, dom->root_crate->get_unwind_glue());
339     if (spawner) {
340         dom->log(rust_log::TASK,
341                  "task 0x%" PRIxPTR
342                  " propagating failure to parent 0x%" PRIxPTR,
343                  this, spawner);
344         spawner->kill();
345     }
346 }
347
348 void
349 rust_task::notify_waiting_tasks()
350 {
351     while (waiting_tasks.length() > 0) {
352         rust_task *t = waiting_tasks.pop()->receiver;
353         if (!t->dead())
354             t->wakeup(this);
355     }
356 }
357
358 uintptr_t
359 rust_task::get_fp() {
360     // sp in any suspended task points to the last callee-saved reg on
361     // the task stack.
362     return get_callee_save_fp((uintptr_t*)rust_sp);
363 }
364
365 uintptr_t
366 rust_task::get_previous_fp(uintptr_t fp) {
367     // fp happens to, coincidentally (!) also point to the last
368     // callee-save on the task stack.
369     return get_callee_save_fp((uintptr_t*)fp);
370 }
371
372 frame_glue_fns*
373 rust_task::get_frame_glue_fns(uintptr_t fp) {
374     fp -= sizeof(uintptr_t);
375     return *((frame_glue_fns**) fp);
376 }
377
378 bool
379 rust_task::running()
380 {
381     return state == &dom->running_tasks;
382 }
383
384 bool
385 rust_task::blocked()
386 {
387     return state == &dom->blocked_tasks;
388 }
389
390 bool
391 rust_task::blocked_on(rust_cond *on)
392 {
393     return blocked() && cond == on;
394 }
395
396 bool
397 rust_task::dead()
398 {
399     return state == &dom->dead_tasks;
400 }
401
402 void
403 rust_task::transition(ptr_vec<rust_task> *src, ptr_vec<rust_task> *dst)
404 {
405     I(dom, state == src);
406     dom->log(rust_log::TASK,
407              "task 0x%" PRIxPTR " state change '%s' -> '%s'",
408              (uintptr_t)this,
409              dom->state_vec_name(src),
410              dom->state_vec_name(dst));
411     dom->remove_task_from_state_vec(src, this);
412     dom->add_task_to_state_vec(dst, this);
413     state = dst;
414 }
415
416 void
417 rust_task::block(rust_cond *on)
418 {
419     I(dom, on);
420     transition(&dom->running_tasks, &dom->blocked_tasks);
421     dom->log(rust_log::TASK,
422              "task 0x%" PRIxPTR " blocking on 0x%" PRIxPTR,
423              (uintptr_t)this,
424              (uintptr_t)on);
425     cond = on;
426 }
427
428 void
429 rust_task::wakeup(rust_cond *from)
430 {
431     transition(&dom->blocked_tasks, &dom->running_tasks);
432     I(dom, cond == from);
433 }
434
435 void
436 rust_task::die()
437 {
438     transition(&dom->running_tasks, &dom->dead_tasks);
439 }
440
441 void
442 rust_task::unblock()
443 {
444     if (blocked())
445         wakeup(cond);
446 }
447
448 rust_crate_cache *
449 rust_task::get_crate_cache(rust_crate const *curr_crate)
450 {
451     if (cache && cache->crate != curr_crate) {
452         dom->log(rust_log::TASK, "switching task crate-cache to crate 0x%"
453                  PRIxPTR, curr_crate);
454         cache->deref();
455         cache = NULL;
456     }
457
458     if (!cache) {
459         dom->log(rust_log::TASK, "fetching cache for current crate");
460         cache = dom->get_cache(curr_crate);
461     }
462     return cache;
463 }
464
465 //
466 // Local Variables:
467 // mode: C++
468 // fill-column: 78;
469 // indent-tabs-mode: nil
470 // c-basic-offset: 4
471 // buffer-file-coding-system: utf-8-unix
472 // compile-command: "make -k -C .. 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
473 // End:
474 //