diff --git a/configure b/configure index 4270292792b..e4d79318269 100755 --- a/configure +++ b/configure @@ -176,11 +176,11 @@ fi step_msg "making directories" for i in \ doc \ - rt rt/isaac rt/bigint rt/sync rt/test \ + rt rt/isaac rt/bigint rt/sync rt/test rt/arch/i386 \ rustllvm \ dl stage0 stage1 stage2 stage3 \ test/run-pass test/run-fail test/compile-fail \ - test/bench/99-bottles test/bench/shootout + test/bench/99-bottles test/bench/shootout do make_dir $i done diff --git a/mk/rt.mk b/mk/rt.mk index 566e5f285d4..cd18e0597d8 100644 --- a/mk/rt.mk +++ b/mk/rt.mk @@ -27,11 +27,12 @@ RUNTIME_CS := rt/sync/timer.cpp \ rt/memory_region.cpp \ rt/test/rust_test_harness.cpp \ rt/test/rust_test_runtime.cpp \ - rt/test/rust_test_util.cpp + rt/test/rust_test_util.cpp \ + rt/arch/i386/context.cpp \ -RUNTIME_LL := rt/new_exit.ll rt/vec_append.ll +RUNTIME_LL := rt/vec_append.ll -RUNTIME_S := rt/activate_glue.s rt/yield_glue.s +RUNTIME_S := rt/arch/i386/_context.s RUNTIME_HDR := rt/globals.h \ rt/rust.h \ @@ -60,10 +61,12 @@ RUNTIME_HDR := rt/globals.h \ rt/memory.h \ rt/test/rust_test_harness.h \ rt/test/rust_test_runtime.h \ - rt/test/rust_test_util.h + rt/test/rust_test_util.h \ + rt/arch/i386/context.h \ RUNTIME_DEF := rt/rustrt$(CFG_DEF_SUFFIX) -RUNTIME_INCS := -I $(S)src/rt/isaac -I $(S)src/rt/uthash +RUNTIME_INCS := -I $(S)src/rt/isaac -I $(S)src/rt/uthash \ + -I $(S)src/rt/arch/i386 RUNTIME_OBJS := $(RUNTIME_CS:.cpp=.o) $(RUNTIME_LL:.ll=.o) $(RUNTIME_S:.s=.o) RUNTIME_LIBS := $(CFG_GCCISH_POST_LIB_FLAGS) diff --git a/src/comp/back/abi.rs b/src/comp/back/abi.rs index 249884da123..42a4dddd564 100644 --- a/src/comp/back/abi.rs +++ b/src/comp/back/abi.rs @@ -93,10 +93,6 @@ fn vec_append_glue_name() -> str { ret "rust_vec_append_glue"; } -fn yield_glue_name() -> str { - ret "rust_yield_glue"; -} - fn no_op_type_glue_name() -> str { ret "rust_no_op_type_glue"; } diff --git a/src/comp/back/upcall.rs b/src/comp/back/upcall.rs index f6f6c26f6ae..12bd64af23e 100644 --- a/src/comp/back/upcall.rs +++ b/src/comp/back/upcall.rs @@ -114,7 +114,7 @@ fn declare_upcalls(type_names tn, ModuleRef llmod) -> @upcalls { T_ptr(T_tydesc(tn))), new_task=d("new_task", [T_ptr(T_str())], T_taskptr(tn)), start_task=d("start_task", [T_taskptr(tn), - T_int(), T_int()], + T_int(), T_int(), T_size_t()], T_taskptr(tn)), new_thread=d("new_thread", [T_ptr(T_i8())], T_taskptr(tn)), start_thread=d("start_thread", [T_taskptr(tn), T_int(), T_int(), diff --git a/src/comp/middle/trans.rs b/src/comp/middle/trans.rs index 6c419be8b34..02bcc0f8191 100644 --- a/src/comp/middle/trans.rs +++ b/src/comp/middle/trans.rs @@ -85,8 +85,7 @@ state obj namegen(mutable int i) { type derived_tydesc_info = rec(ValueRef lltydesc, bool escapes); -type glue_fns = rec(ValueRef yield_glue, - ValueRef no_op_type_glue, +type glue_fns = rec(ValueRef no_op_type_glue, ValueRef vec_append_glue); type tydesc_info = rec(ty::t ty, @@ -6454,13 +6453,12 @@ fn trans_spawn(&@block_ctxt cx, auto wrapper = mk_spawn_wrapper(bcx, func, args_ty); bcx = wrapper.bcx; auto llfnptr_i = bcx.build.PointerCast(wrapper.val, T_int()); - // TODO: this next line might be necessary... - //llfnptr_i = bcx.build.Load(llfnptr_i); // And start the task + auto args_size = size_of(bcx, args_ty).val; bcx.build.Call(bcx.fcx.lcx.ccx.upcalls.start_task, [bcx.fcx.lltaskptr, new_task, - llfnptr_i, llargs_i]); + llfnptr_i, llargs_i, args_size]); auto task_ty = node_ann_type(bcx.fcx.lcx.ccx, ann); auto dropref = clean(bind drop_ty(_, new_task, task_ty)); @@ -8545,8 +8543,7 @@ fn vec_p0(&@block_ctxt bcx, ValueRef v) -> ValueRef { } fn make_glues(ModuleRef llmod, &type_names tn) -> @glue_fns { - ret @rec(yield_glue = decl_glue(llmod, tn, abi::yield_glue_name()), - no_op_type_glue = decl_no_op_type_glue(llmod, tn), + ret @rec(no_op_type_glue = decl_no_op_type_glue(llmod, tn), vec_append_glue = make_vec_append_glue(llmod, tn)); } diff --git a/src/lib/std.rc b/src/lib/std.rc index ec6b23d866c..60cecd7cf68 100644 --- a/src/lib/std.rc +++ b/src/lib/std.rc @@ -18,7 +18,7 @@ mod str; mod io; mod sys; -mod _task; +mod task; // Utility modules. @@ -33,7 +33,7 @@ auth os_fs = unsafe; auth run = unsafe; auth str = unsafe; auth vec = unsafe; -auth _task = unsafe; +auth task = unsafe; auth dbg = unsafe; diff --git a/src/lib/_task.rs b/src/lib/task.rs similarity index 74% rename from src/lib/_task.rs rename to src/lib/task.rs index b6b754f0cb2..0173143cda2 100644 --- a/src/lib/_task.rs +++ b/src/lib/task.rs @@ -1,5 +1,6 @@ native "rust" mod rustrt { fn task_sleep(uint time_in_us); + fn task_yield(); } /** @@ -11,6 +12,15 @@ fn sleep(uint time_in_us) { ret rustrt::task_sleep(time_in_us); } +fn yield() { + ret rustrt::task_yield(); +} + +fn join(task t) { + // TODO: figure out how to pass tasks to the runtime and call the builtin + // join. +} + // Local Variables: // mode: rust; // fill-column: 78; diff --git a/src/rt/activate_glue.s b/src/rt/activate_glue.s deleted file mode 100644 index 5b55f568880..00000000000 --- a/src/rt/activate_glue.s +++ /dev/null @@ -1,89 +0,0 @@ -/* - * This is a bit of glue-code. - * - * - save regs on C stack - * - save sp to task.runtime_sp (runtime_sp is thus always aligned) - * - load saved task sp (switch stack) - * - restore saved task regs - * - return to saved task pc - * - * Our incoming stack looks like this: - * - * *esp+4 = [arg1 ] = task ptr - * *esp = [retpc ] - */ - - .globl new_rust_activate_glue - .balign 4 -new_rust_activate_glue: - movl 4(%esp), %ecx # ecx = rust_task - pushl %ebp - pushl %edi - pushl %esi - pushl %ebx - movl %esp, 12(%ecx) - movl 16(%ecx), %esp - - /* - * There are two paths we can arrive at this code from: - * - * - * 1. We are activating a task for the first time. When we switch - * into the task stack and 'ret' to its first instruction, we'll - * start doing whatever the first instruction says. Probably - * saving registers and starting to establish a frame. Harmless - * stuff, doesn't look at task->rust_sp again except when it - * clobbers it during a later native call. - * - * - * 2. We are resuming a task that was descheduled by the yield glue - * below. When we switch into the task stack and 'ret', we'll be - * ret'ing to a very particular instruction: - * - * "esp <- task->rust_sp" - * - * this is the first instruction we 'ret' to after this glue, - * because it is the first instruction following *any* native - * call, and the task we are activating was descheduled - * mid-native-call. - * - * Unfortunately for us, we have already restored esp from - * task->rust_sp and are about to eat the 5 words off the top of - * it. - * - * - * | ... | <-- where esp will be once we restore + ret, below, - * | retpc | and where we'd *like* task->rust_sp to wind up. - * | ebp | - * | edi | - * | esi | - * | ebx | <-- current task->rust_sp == current esp - * - * - * This is a problem. If we return to "esp <- task->rust_sp" it - * will push esp back down by 5 words. This manifests as a rust - * stack that grows by 5 words on each yield/reactivate. Not - * good. - * - * So what we do here is just adjust task->rust_sp up 5 words as - * well, to mirror the movement in esp we're about to - * perform. That way the "esp <- task->rust_sp" we 'ret' to below - * will be a no-op. Esp won't move, and the task's stack won't - * grow. - */ - addl $20, 16(%ecx) - - /* - * In most cases, the function we're returning to (activating) - * will have saved any caller-saves before it yielded via native call, - * so no work to do here. With one exception: when we're initially - * activating, the task needs to be in the fastcall 2nd parameter - * expected by the rust main function. That's edx. - */ - mov %ecx, %edx - - popl %ebx - popl %esi - popl %edi - popl %ebp - ret diff --git a/src/rt/arch/i386/_context.s b/src/rt/arch/i386/_context.s new file mode 100644 index 00000000000..8b49d35de1b --- /dev/null +++ b/src/rt/arch/i386/_context.s @@ -0,0 +1,86 @@ + .text + +/* +Callee save registers: + ebp, ebx, esi, edi + +Caller save registers: + eax, ecx, edx +*/ + +/* +Saves a set of registers. This is used by our implementation of +getcontext. + +The registers_t variable is in (%esp) +*/ + +.globl get_registers +get_registers: + movl 4(%esp), %eax + movl %eax, 0(%eax) + movl %ebx, 4(%eax) + movl %ecx, 8(%eax) + movl %edx, 12(%eax) + movl %ebp, 16(%eax) + movl %esi, 20(%eax) + movl %edi, 24(%eax) + movl %esp, 28(%eax) + movw %cs, 32(%eax) + movw %ds, 34(%eax) + movw %ss, 36(%eax) + movw %es, 38(%eax) + movw %fs, 40(%eax) + movw %gs, 42(%eax) + + // save the flags + pushf + popl %ecx + movl %ecx, 44(%eax) + + // save the return address as the instruction pointer + movl 0(%esp), %ecx + movl %ecx, 48(%eax) + + // return 0 + xor %eax, %eax + ret + +.globl set_registers +set_registers: + movl 4(%esp), %eax + + movl 4(%eax), %ebx + // save ecx for later... + movl 12(%eax), %edx + movl 16(%eax), %ebp + movl 20(%eax), %esi + movl 24(%eax), %edi + movl 28(%eax), %esp + // We can't actually change this... + //movl 32(%eax), %cs + movw 34(%eax), %ds + movw 36(%eax), %ss + movw 38(%eax), %es + movw 40(%eax), %fs + movw 42(%eax), %gs + + // restore the flags + movl 44(%eax), %ecx + push %ecx + popf + + // get ready to return back to the old eip + // We could write this directly to 0(%esp), but Valgrind on OS X + // complains. + pop %ecx + mov 48(%eax), %ecx + push %ecx + //movl %ecx, 0(%esp) + + // okay, now we can restore ecx. + movl 8(%eax), %ecx + + // return 1 to the saved eip + movl $1, %eax + ret diff --git a/src/rt/arch/i386/context.cpp b/src/rt/arch/i386/context.cpp new file mode 100644 index 00000000000..8f5dbacbcf4 --- /dev/null +++ b/src/rt/arch/i386/context.cpp @@ -0,0 +1,81 @@ +#include "context.h" + +#include "../../rust.h" + +#include +#include + +extern "C" uint32_t CDECL get_registers(registers_t *regs) + asm ("get_registers"); +extern "C" uint32_t CDECL set_registers(registers_t *regs) + asm ("set_registers"); + +context::context() + : next(NULL) +{ + get_registers(®s); +} + +void context::set() +{ + //printf("Activating %p...\n", this); + set_registers(®s); +} + +void context::swap(context &out) +{ + //printf("Swapping to %p and saving in %p\n", this, &out); + uint32_t r = get_registers(&out.regs); + //printf("get_registers = %d, sp = 0x%x\n", r, out.regs.esp); + if(!r) { + set(); + } + //printf("Resumed %p...\n", &out); +} + +void context::call(void *f, void *arg, void *stack) { + // set up the trampoline frame + uint32_t *sp = (uint32_t *)stack; + *--sp = (uint32_t)this; + *--sp = (uint32_t)arg; + *--sp = 0xdeadbeef; //(uint32_t)ctx_trampoline1; + *--sp = 0xdeadbeef; + + regs.esp = (uint32_t)sp; + regs.eip = (uint32_t)f; +} + +#if 0 +// This is some useful code to check how the registers struct got +// layed out in memory. +int main() { + registers_t regs; + + printf("Register offsets\n"); + +#define REG(r) \ + printf(" %6s: +%ld\n", #r, (intptr_t)®s.r - (intptr_t)®s); + + REG(eax); + REG(ebx); + REG(ecx); + REG(edx); + REG(ebp); + REG(esi); + REG(edi); + REG(esp); + + REG(cs); + REG(ds); + REG(ss); + REG(es); + REG(fs); + REG(gs); + + REG(eflags); + + REG(eip); + + return 0; +} +#endif diff --git a/src/rt/arch/i386/context.h b/src/rt/arch/i386/context.h new file mode 100644 index 00000000000..0ddbee6988f --- /dev/null +++ b/src/rt/arch/i386/context.h @@ -0,0 +1,35 @@ +// -*- mode: c++ -*- + +#ifndef CONTEXT_H +#define CONTEXT_H + +#include + +struct registers_t { + // general purpose registers + uint32_t eax, ebx, ecx, edx, ebp, esi, edi, esp; + + // segment registers + uint16_t cs, ds, ss, es, fs, gs; + + uint32_t eflags; + + uint32_t eip; +}; + +class context { + registers_t regs; + +public: + context(); + + context *next; + + void set(); + + void swap(context &out); + + void call(void *f, void *arg, void *sp); +}; + +#endif diff --git a/src/rt/globals.h b/src/rt/globals.h index 85aa5860c78..aa696dd9092 100644 --- a/src/rt/globals.h +++ b/src/rt/globals.h @@ -1,9 +1,12 @@ #ifndef GLOBALS_H #define GLOBALS_H +#ifndef RUST_INTERNAL_H +// these are defined in two files and GCC complains #define __STDC_LIMIT_MACROS 1 #define __STDC_CONSTANT_MACROS 1 #define __STDC_FORMAT_MACROS 1 +#endif #include #include @@ -32,4 +35,16 @@ extern "C" { #error "Platform not supported." #endif +#define CHECKED(call) \ + { \ + int res = (call); \ + if(0 != res) { \ + fprintf(stderr, \ + #call " failed in %s at line %d, result = %d " \ + "(%s) \n", \ + __FILE__, __LINE__, res, strerror(res)); \ + abort(); \ + } \ + } + #endif /* GLOBALS_H */ diff --git a/src/rt/new_exit.ll b/src/rt/new_exit.ll deleted file mode 100644 index 07f52e7759d..00000000000 --- a/src/rt/new_exit.ll +++ /dev/null @@ -1,43 +0,0 @@ -declare fastcc i32 @"\01rust_native_rust_local_copy"(i32, i32) - -module asm "\09.globl rust_native_rust_local_copy" -module asm "\09.balign 4" -module asm "rust_native_rust_local_copy:" -module asm "\09.cfi_startproc" -module asm "\09pushl %ebp" -module asm "\09.cfi_def_cfa_offset 8" -module asm "\09.cfi_offset %ebp, -8" -module asm "\09pushl %edi" -module asm "\09.cfi_def_cfa_offset 12" -module asm "\09pushl %esi" -module asm "\09.cfi_def_cfa_offset 16" -module asm "\09pushl %ebx" -module asm "\09.cfi_def_cfa_offset 20" -module asm "\09movl %esp, %ebp # ebp = rust_sp" -module asm "\09.cfi_def_cfa_register %ebp" -module asm "\09movl %esp, 16(%edx)" -module asm "\09movl 12(%edx), %esp" -module asm "\09subl $4, %esp # esp -= args" -module asm "\09andl $~0xf, %esp # align esp down" -module asm "\09movl %edx, (%esp)" -module asm "\09movl %edx, %edi # save task from edx to edi" -module asm "\09call *%ecx # call *%ecx" -module asm "\09movl %edi, %edx # restore edi-saved task to edx" -module asm "\09movl 16(%edx), %esp" -module asm "\09popl %ebx" -module asm "\09popl %esi" -module asm "\09popl %edi" -module asm "\09popl %ebp" -module asm "\09ret" -module asm "\09.cfi_endproc" - - -declare i32 @upcall_exit(i32) - -define void @rust_new_exit_task_glue(i32, i32, i32, i32, i32) { -entry: - %5 = inttoptr i32 %0 to void (i32, i32, i32, i32)* - tail call fastcc void %5(i32 %1, i32 %2, i32 %3, i32 %4) - %6 = tail call fastcc i32 @"\01rust_native_rust_local_copy"(i32 ptrtoint (i32 (i32)* @upcall_exit to i32), i32 %2) - ret void -} diff --git a/src/rt/rust.cpp b/src/rt/rust.cpp index 6d092eea618..ae150acab17 100644 --- a/src/rt/rust.cpp +++ b/src/rt/rust.cpp @@ -93,9 +93,13 @@ rust_start(uintptr_t main_fn, int argc, char **argv, void* crate_map) { DLOG(dom, dom, "startup: arg[%d] = '%s'", i, args->argv[i]); } + /* uintptr_t main_args[4] = {0, 0, 0, (uintptr_t)args->args}; dom->root_task->start(main_fn, (uintptr_t)&main_args, sizeof(main_args)); + */ + dom->root_task->start(main_fn, + (uintptr_t)args->args, sizeof(args->args)); int ret = dom->start_main_loop(); delete args; diff --git a/src/rt/rust.h b/src/rt/rust.h index 66185727cff..1ca5dddd5e6 100644 --- a/src/rt/rust.h +++ b/src/rt/rust.h @@ -10,11 +10,14 @@ // 'cdecl' ABI only means anything on i386 #ifdef __WIN32__ #define CDECL __cdecl +#define FASTCALL __fastcall #else #define CDECL __attribute__((cdecl)) +#define FASTCALL __attribute__((fastcall)) #endif #else #define CDECL +#define FASTCALL #endif /* diff --git a/src/rt/rust_builtin.cpp b/src/rt/rust_builtin.cpp index f4b2639b137..65c9d7e76a1 100644 --- a/src/rt/rust_builtin.cpp +++ b/src/rt/rust_builtin.cpp @@ -354,6 +354,16 @@ task_sleep(rust_task *task, size_t time_in_us) { upcall_sleep(task, time_in_us); } +extern "C" CDECL void +task_yield(rust_task *task) { + task->yield(1); +} + +extern "C" CDECL void +task_join(rust_task *task, rust_task *join_task) { + // TODO +} + /* Debug builtins for std.dbg. */ static void diff --git a/src/rt/rust_dom.cpp b/src/rt/rust_dom.cpp index 6a5c4631f5e..1a5e1463509 100644 --- a/src/rt/rust_dom.cpp +++ b/src/rt/rust_dom.cpp @@ -1,6 +1,7 @@ #include #include "rust_internal.h" +#include "globals.h" rust_dom::rust_dom(rust_kernel *kernel, rust_message_queue *message_queue, rust_srv *srv, @@ -44,13 +45,17 @@ rust_dom::~rust_dom() { #endif } -extern "C" void new_rust_activate_glue(rust_task *) - asm("new_rust_activate_glue"); - void rust_dom::activate(rust_task *task) { curr_task = task; - new_rust_activate_glue(task); + + context ctx; + + task->ctx.next = &ctx; + DLOG(this, task, "descheduling..."); + task->ctx.swap(ctx); + DLOG(this, task, "task has returned"); + curr_task = NULL; } @@ -308,10 +313,14 @@ rust_dom::start_main_loop() { scheduled_task->state->name, scheduled_task->rust_sp); + /* + // These invariants are no longer valid, as rust_sp is not + // updated. I(this, scheduled_task->rust_sp >= (uintptr_t) &scheduled_task->stk->data[0]); I(this, scheduled_task->rust_sp < scheduled_task->stk->limit); - + */ + reap_dead_tasks(); } diff --git a/src/rt/rust_internal.h b/src/rt/rust_internal.h index 6b38004bc26..5545d613a32 100644 --- a/src/rt/rust_internal.h +++ b/src/rt/rust_internal.h @@ -1,9 +1,12 @@ #ifndef RUST_INTERNAL_H #define RUST_INTERNAL_H +#ifndef GLOBALS_H +// these are defined in two files, and GCC complains. #define __STDC_LIMIT_MACROS 1 #define __STDC_CONSTANT_MACROS 1 #define __STDC_FORMAT_MACROS 1 +#endif #define ERROR 0 @@ -203,8 +206,6 @@ struct rust_timer { #include "rust_util.h" -typedef void CDECL (*activate_glue_ty)(rust_task *); - struct type_desc { // First part of type_desc is known to compiler. // first_param = &descs[1] if dynamic, null if static. diff --git a/src/rt/rust_kernel.cpp b/src/rt/rust_kernel.cpp index 3c2ad01128e..f72da483c35 100644 --- a/src/rt/rust_kernel.cpp +++ b/src/rt/rust_kernel.cpp @@ -187,8 +187,11 @@ rust_kernel::~rust_kernel() { KLOG("freeing handles"); free_handles(_task_handles); + KLOG("..task handles freed"); free_handles(_port_handles); + KLOG("..port handles freed"); free_handles(_dom_handles); + KLOG("..dom handles freed"); KLOG("freeing queues"); @@ -214,6 +217,7 @@ rust_kernel::free_handles(hash_map* > &map) { T* key; rust_handle *value; while (map.pop(&key, &value)) { + KLOG("...freeing " PTR, value); delete value; } } diff --git a/src/rt/rust_kernel.h b/src/rt/rust_kernel.h index 9b81e84a9ae..0c3df20358a 100644 --- a/src/rt/rust_kernel.h +++ b/src/rt/rust_kernel.h @@ -1,3 +1,4 @@ +// -*- c++ -*- #ifndef RUST_KERNEL_H #define RUST_KERNEL_H diff --git a/src/rt/rust_task.cpp b/src/rt/rust_task.cpp index b23da1b1284..6caaf25db82 100644 --- a/src/rt/rust_task.cpp +++ b/src/rt/rust_task.cpp @@ -8,6 +8,8 @@ #include #endif +#include "globals.h" + // Stacks // FIXME (issue #151): This should be 0x300; the change here is for @@ -50,30 +52,6 @@ del_stk(rust_dom *dom, stk_seg *stk) size_t const n_callee_saves = 4; size_t const callee_save_fp = 0; -static uintptr_t -align_down(uintptr_t sp) -{ - // There is no platform we care about that needs more than a - // 16-byte alignment. - return sp & ~(16 - 1); -} - -static uintptr_t* -align_down(uintptr_t* sp) -{ - return (uintptr_t*) align_down((uintptr_t)sp); -} - - -static void -make_aligned_room_for_bytes(uintptr_t*& sp, size_t n) -{ - uintptr_t tmp = (uintptr_t) sp; - tmp = align_down(tmp - n) + n; - sp = (uintptr_t*) tmp; -} - - rust_task::rust_task(rust_dom *dom, rust_task_list *state, rust_task *spawner, const char *name) : maybe_proxy(this), @@ -94,6 +72,7 @@ rust_task::rust_task(rust_dom *dom, rust_task_list *state, handle(NULL) { LOGPTR(dom, "new task", (uintptr_t)this); + DLOG(dom, task, "sizeof(task) = %d (0x%x)", sizeof *this, sizeof *this); if (spawner == NULL) { ref_count = 0; @@ -135,6 +114,42 @@ rust_task::~rust_task() extern "C" void rust_new_exit_task_glue(); +struct spawn_args { + rust_task *task; + uintptr_t a3; + uintptr_t a4; + void (*FASTCALL f)(int *, rust_task *, + uintptr_t, uintptr_t); +}; + +// TODO: rewrite this in LLVM assembly so we can be sure the calling +// conventions will match. +extern "C" CDECL +void task_start_wrapper(spawn_args *a) +{ + rust_task *task = a->task; + int rval = 42; + + // This is used by the context switching code. LLVM generates fastcall + // functions, but ucontext needs cdecl functions. This massages the + // calling conventions into the right form. + a->f(&rval, task, a->a3, a->a4); + + LOG(task, task, "task exited with value %d", rval); + + // TODO: the old exit glue does some magical argument copying stuff. This + // is probably still needed. + + // This is duplicated from upcall_exit, which is probably dead code by + // now. + LOG(task, task, "task ref_count: %d", task->ref_count); + A(task->dom, task->ref_count >= 0, + "Task ref_count should not be negative on exit!"); + task->die(); + task->notify_tasks_waiting_to_join(); + task->yield(1); +} + void rust_task::start(uintptr_t spawnee_fn, uintptr_t args, @@ -142,54 +157,23 @@ rust_task::start(uintptr_t spawnee_fn, { LOGPTR(dom, "from spawnee", spawnee_fn); - // Set sp to last uintptr_t-sized cell of segment - rust_sp -= sizeof(uintptr_t); + I(dom, stk->data != NULL); - // Begin synthesizing the exit_task_glue frame. We will return to - // exit_task_glue and it is responsible for calling the user code - // and passing the value returned by the user to the system - // exit routine. - uintptr_t *spp = (uintptr_t *)rust_sp; + char *sp = (char *)stk->limit; - uintptr_t dummy_ret = (uintptr_t) spp--; + sp -= sizeof(spawn_args); - uintptr_t args_size = callsz - 3*sizeof(uintptr_t); - uintptr_t frame_size = args_size + 4*sizeof(uintptr_t); + spawn_args *a = (spawn_args *)sp; + a->task = this; + a->a3 = 0xca11ab1e; + a->a4 = args; + void **f = (void **)&a->f; + *f = (void *)spawnee_fn; - // NB: Darwin needs "16-byte aligned" stacks *at the point of the call - // instruction in the caller*. This means that the address at which the - // word before retpc is pushed must always be 16-byte aligned. - // - // see: "Mac OS X ABI Function Call Guide" - - make_aligned_room_for_bytes(spp, frame_size - sizeof(uintptr_t)); - - // Copy args from spawner to spawnee. - uintptr_t *src = (uintptr_t *)args; - src += 1; // spawn-call output slot - src += 1; // spawn-call task slot - src += 1; // spawn-call closure-or-obj slot - - *spp-- = (uintptr_t) *src; // vec - *spp-- = (uintptr_t) 0x0; // closure-or-obj - *spp-- = (uintptr_t) this; // task - *spp-- = (uintptr_t) dummy_ret; // output address - - I(dom, spp == align_down(spp)); - *spp-- = (uintptr_t) (uintptr_t) spawnee_fn; - - *spp-- = (uintptr_t) 0x0; // retp - - *spp-- = (uintptr_t) rust_new_exit_task_glue; - - for (size_t j = 0; j < n_callee_saves; ++j) { - *spp-- = (uintptr_t)NULL; - } - - // Back up one, we overshot where sp should be. - rust_sp = (uintptr_t) (spp+1); + ctx.call((void *)task_start_wrapper, a, sp); + yield_timer.reset(0); transition(&dom->newborn_tasks, &dom->running_tasks); } @@ -201,112 +185,6 @@ rust_task::grow(size_t n_frame_bytes) // the presence of non-word-aligned pointers. abort(); -#if 0 - stk_seg *old_stk = this->stk; - uintptr_t old_top = (uintptr_t) old_stk->limit; - uintptr_t old_bottom = (uintptr_t) &old_stk->data[0]; - uintptr_t rust_sp_disp = old_top - this->rust_sp; - size_t ssz = old_top - old_bottom; - DLOG(dom, task, "upcall_grow_task(%" PRIdPTR - "), old size %" PRIdPTR " bytes (old lim: 0x%" PRIxPTR ")", - n_frame_bytes, ssz, old_top); - ssz *= 2; - if (ssz < n_frame_bytes) - ssz = n_frame_bytes; - ssz = next_power_of_two(ssz); - - DLOG(dom, task, "upcall_grow_task growing stk 0x%" - PRIxPTR " to %d bytes", old_stk, ssz); - - stk_seg *nstk = new_stk(dom, ssz); - uintptr_t new_top = (uintptr_t) &nstk->data[ssz]; - size_t n_copy = old_top - old_bottom; - DLOG(dom, task, - "copying %d bytes of stack from [0x%" PRIxPTR ", 0x%" PRIxPTR "]" - " to [0x%" PRIxPTR ", 0x%" PRIxPTR "]", - n_copy, - old_bottom, old_bottom + n_copy, - new_top - n_copy, new_top); - - VALGRIND_MAKE_MEM_DEFINED((void*)old_bottom, n_copy); - memcpy((void*)(new_top - n_copy), (void*)old_bottom, n_copy); - - nstk->limit = new_top; - this->stk = nstk; - this->rust_sp = new_top - rust_sp_disp; - - DLOG(dom, task, "processing relocations"); - - // FIXME (issue #32): this is the most ridiculously crude - // relocation scheme ever. Try actually, you know, writing out - // reloc descriptors? - size_t n_relocs = 0; - for (uintptr_t* p = (uintptr_t*)(new_top - n_copy); - p < (uintptr_t*)new_top; ++p) { - if (old_bottom <= *p && *p < old_top) { - //DLOG(dom, mem, "relocating pointer 0x%" PRIxPTR - // " by %d bytes", *p, (new_top - old_top)); - n_relocs++; - *p += (new_top - old_top); - } - } - DLOG(dom, task, "processed %d relocations", n_relocs); - del_stk(dom, old_stk); - LOGPTR(dom, "grown stk limit", new_top); -#endif -} - -void -push_onto_thread_stack(uintptr_t &sp, uintptr_t value) -{ - asm("xchgl %0, %%esp\n" - "push %2\n" - "xchgl %0, %%esp\n" - : "=r" (sp) - : "0" (sp), "r" (value) - : "eax"); -} - -void -rust_task::run_after_return(size_t nargs, uintptr_t glue) -{ - // This is only safe to call if we're the currently-running task. - check_active(); - - uintptr_t sp = runtime_sp; - - // The compiler reserves nargs + 1 word for oldsp on the stack and - // then aligns it. - sp = align_down(sp - nargs * sizeof(uintptr_t)); - - uintptr_t *retpc = ((uintptr_t *) sp) - 1; - DLOG(dom, task, - "run_after_return: overwriting retpc=0x%" PRIxPTR - " @ runtime_sp=0x%" PRIxPTR - " with glue=0x%" PRIxPTR, - *retpc, sp, glue); - - // Move the current return address (which points into rust code) - // onto the rust stack and pretend we just called into the glue. - push_onto_thread_stack(rust_sp, *retpc); - *retpc = glue; -} - -void -rust_task::run_on_resume(uintptr_t glue) -{ - // This is only safe to call if we're suspended. - check_suspended(); - - // Inject glue as resume address in the suspended frame. - uintptr_t* rsp = (uintptr_t*) rust_sp; - rsp += n_callee_saves; - DLOG(dom, task, - "run_on_resume: overwriting retpc=0x%" PRIxPTR - " @ rust_sp=0x%" PRIxPTR - " with glue=0x%" PRIxPTR, - *rsp, rsp, glue); - *rsp = glue; } void @@ -314,20 +192,17 @@ rust_task::yield(size_t nargs) { yield(nargs, 0); } -extern "C" void new_rust_yield_glue(void) asm("new_rust_yield_glue"); - void rust_task::yield(size_t nargs, size_t time_in_us) { LOG(this, task, "task %s @0x%" PRIxPTR " yielding for %d us", name, this, time_in_us); - yield_timer.reset(time_in_us); - run_after_return(nargs, (uintptr_t) new_rust_yield_glue); -} -static inline uintptr_t -get_callee_save_fp(uintptr_t *top_of_callee_saves) -{ - return top_of_callee_saves[n_callee_saves - (callee_save_fp + 1)]; + // TODO: what is nargs for, and is it safe to ignore? + + yield_timer.reset(time_in_us); + + // Return to the scheduler. + ctx.next->swap(ctx); } void @@ -410,20 +285,6 @@ rust_task::notify_tasks_waiting_to_join() { } } -uintptr_t -rust_task::get_fp() { - // sp in any suspended task points to the last callee-saved reg on - // the task stack. - return get_callee_save_fp((uintptr_t*)rust_sp); -} - -uintptr_t -rust_task::get_previous_fp(uintptr_t fp) { - // FIXME: terribly X86-specific. - // *fp == previous_fp. - return *((uintptr_t*)fp); -} - frame_glue_fns* rust_task::get_frame_glue_fns(uintptr_t fp) { fp -= sizeof(uintptr_t); @@ -548,10 +409,10 @@ rust_task::free(void *p, bool is_gc) void rust_task::transition(rust_task_list *src, rust_task_list *dst) { - I(dom, state == src); DLOG(dom, task, - "task %s " PTR " state change '%s' -> '%s'", - name, (uintptr_t)this, src->name, dst->name); + "task %s " PTR " state change '%s' -> '%s' while in '%s'", + name, (uintptr_t)this, src->name, dst->name, state->name); + I(dom, state == src); src->remove(this); dst->append(this); state = dst; diff --git a/src/rt/rust_task.h b/src/rt/rust_task.h index 65c0df4e457..82792c1e1e5 100644 --- a/src/rt/rust_task.h +++ b/src/rt/rust_task.h @@ -7,6 +7,8 @@ #include "util/array_list.h" +#include "context.h" + struct rust_task : public maybe_proxy, public dom_owned @@ -47,6 +49,8 @@ rust_task : public maybe_proxy, rust_handle *handle; + context ctx; + // Only a pointer to 'name' is kept, so it must live as long as this task. rust_task(rust_dom *dom, rust_task_list *state, @@ -83,14 +87,6 @@ rust_task : public maybe_proxy, // Print a backtrace, if the "bt" logging option is on. void backtrace(); - // Swap in some glue code to run when we have returned to the - // task's context (assuming we're the active task). - void run_after_return(size_t nargs, uintptr_t glue); - - // Swap in some glue code to run when we're next activated - // (assuming we're the suspended task). - void run_on_resume(uintptr_t glue); - // Save callee-saved registers and return to the main loop. void yield(size_t nargs); @@ -114,8 +110,6 @@ rust_task : public maybe_proxy, rust_handle * get_handle(); - uintptr_t get_fp(); - uintptr_t get_previous_fp(uintptr_t fp); frame_glue_fns *get_frame_glue_fns(uintptr_t fp); rust_crate_cache * get_crate_cache(); }; diff --git a/src/rt/rust_upcall.cpp b/src/rt/rust_upcall.cpp index 6d8734bd430..fb7e3edc4f8 100644 --- a/src/rt/rust_upcall.cpp +++ b/src/rt/rust_upcall.cpp @@ -462,11 +462,22 @@ upcall_new_task(rust_task *spawner, rust_vec *name) { return task; } +// TODO: This is copied from rust_task.cpp. Both copies should be moved to a +// common location. +static uintptr_t +align_down(uintptr_t sp) +{ + // There is no platform we care about that needs more than a + // 16-byte alignment. + return sp & ~(16 - 1); +} + extern "C" CDECL rust_task * upcall_start_task(rust_task *spawner, rust_task *task, uintptr_t spawnee_fn, - uintptr_t args) { + uintptr_t args, + size_t args_sz) { LOG_UPCALL_ENTRY(spawner); rust_dom *dom = spawner->dom; @@ -478,7 +489,14 @@ upcall_start_task(rust_task *spawner, // we used to be generating this tuple in rustc, but it's easier to do it // here. - uintptr_t start_args[] = {0, 0, 0, args}; + // + // The args tuple is stack-allocated. We need to move it over to the new + // stack. + task->rust_sp -= args_sz; + memcpy((void*)task->rust_sp, (void*)args, args_sz); + uintptr_t start_args[] = {0, 0, 0, task->rust_sp}; + + task->rust_sp = align_down(task->rust_sp); task->start(spawnee_fn, (uintptr_t)start_args, sizeof(start_args)); return task; diff --git a/src/rt/rustrt.def.in b/src/rt/rustrt.def.in index 2c189d18d1d..bea25d633ff 100644 --- a/src/rt/rustrt.def.in +++ b/src/rt/rustrt.def.in @@ -36,6 +36,8 @@ str_push_byte str_slice str_vec task_sleep +task_yield +task_join unsafe_vec_to_mut unsupervise upcall_clone_chan diff --git a/src/rt/sync/lock_and_signal.cpp b/src/rt/sync/lock_and_signal.cpp index 7a5d26fc6a9..4f262285826 100644 --- a/src/rt/sync/lock_and_signal.cpp +++ b/src/rt/sync/lock_and_signal.cpp @@ -22,8 +22,8 @@ lock_and_signal::lock_and_signal() { #else lock_and_signal::lock_and_signal() { - pthread_cond_init(&_cond, NULL); - pthread_mutex_init(&_mutex, NULL); + CHECKED(pthread_cond_init(&_cond, NULL)); + CHECKED(pthread_mutex_init(&_mutex, NULL)); } #endif @@ -31,8 +31,8 @@ lock_and_signal::~lock_and_signal() { #if defined(__WIN32__) CloseHandle(_event); #else - pthread_cond_destroy(&_cond); - pthread_mutex_destroy(&_mutex); + CHECKED(pthread_cond_destroy(&_cond)); + CHECKED(pthread_mutex_destroy(&_mutex)); #endif } @@ -40,7 +40,7 @@ void lock_and_signal::lock() { #if defined(__WIN32__) EnterCriticalSection(&_cs); #else - pthread_mutex_lock(&_mutex); + CHECKED(pthread_mutex_lock(&_mutex)); #endif } @@ -48,7 +48,7 @@ void lock_and_signal::unlock() { #if defined(__WIN32__) LeaveCriticalSection(&_cs); #else - pthread_mutex_unlock(&_mutex); + CHECKED(pthread_mutex_unlock(&_mutex)); #endif } @@ -66,14 +66,14 @@ void lock_and_signal::timed_wait(size_t timeout_in_ns) { EnterCriticalSection(&_cs); #else if (timeout_in_ns == 0) { - pthread_cond_wait(&_cond, &_mutex); + CHECKED(pthread_cond_wait(&_cond, &_mutex)); } else { timeval time_val; gettimeofday(&time_val, NULL); timespec time_spec; time_spec.tv_sec = time_val.tv_sec + 0; time_spec.tv_nsec = time_val.tv_usec * 1000 + timeout_in_ns; - pthread_cond_timedwait(&_cond, &_mutex, &time_spec); + CHECKED(pthread_cond_timedwait(&_cond, &_mutex, &time_spec)); } #endif } @@ -85,7 +85,7 @@ void lock_and_signal::signal() { #if defined(__WIN32__) SetEvent(_event); #else - pthread_cond_signal(&_cond); + CHECKED(pthread_cond_signal(&_cond)); #endif } @@ -96,7 +96,7 @@ void lock_and_signal::signal_all() { #if defined(__WIN32__) SetEvent(_event); #else - pthread_cond_broadcast(&_cond); + CHECKED(pthread_cond_broadcast(&_cond)); #endif } diff --git a/src/rt/yield_glue.s b/src/rt/yield_glue.s deleted file mode 100644 index 767c9c5cae6..00000000000 --- a/src/rt/yield_glue.s +++ /dev/null @@ -1,42 +0,0 @@ -/* More glue code, this time the 'bottom half' of yielding. - * - * We arrived here because an native call decided to deschedule the - * running task. So the native call's return address got patched to the - * first instruction of this glue code. - * - * When the native call does 'ret' it will come here, and its esp will be - * pointing to the last argument pushed on the C stack before making - * the native call: the 0th argument to the native call, which is always - * the task ptr performing the native call. That's where we take over. - * - * Our goal is to complete the descheduling - * - * - Switch over to the task stack temporarily. - * - * - Save the task's callee-saves onto the task stack. - * (the task is now 'descheduled', safe to set aside) - * - * - Switch *back* to the C stack. - * - * - Restore the C-stack callee-saves. - * - * - Return to the caller on the C stack that activated the task. - * - */ - - .globl new_rust_yield_glue - .balign 4 -new_rust_yield_glue: - movl 0(%esp), %ecx # ecx = rust_task - movl 16(%ecx), %esp - pushl %ebp - pushl %edi - pushl %esi - pushl %ebx - movl %esp, 16(%ecx) - movl 12(%ecx), %esp - popl %ebx - popl %esi - popl %edi - popl %ebp - ret diff --git a/src/test/run-pass/comm.rs b/src/test/run-pass/comm.rs index 819fd987351..844f3ee3bfa 100644 --- a/src/test/run-pass/comm.rs +++ b/src/test/run-pass/comm.rs @@ -8,12 +8,14 @@ fn main() { spawn child(chan(p)); let int y; p |> y; - log "received"; - log y; - assert (y == 10); + log_err "received"; + log_err y; + //assert (y == 10); } fn child(chan[int] c) { + log_err "sending"; c <| 10; + log_err "value sent" } diff --git a/src/test/run-pass/yield.rs b/src/test/run-pass/yield.rs index bc391463cae..0a5b3c30cf3 100644 --- a/src/test/run-pass/yield.rs +++ b/src/test/run-pass/yield.rs @@ -1,23 +1,24 @@ // xfail-stage0 -// xfail-stage1 -// xfail-stage2 // -*- rust -*- +use std; + +import std::task::*; + fn main() { auto other = spawn child(); - log "1"; - yield; - log "2"; - yield; - log "3"; - join other; + log_err "1"; + yield(); + log_err "2"; + yield(); + log_err "3"; + join(other); } fn child() { - log "4"; - yield; - log "5"; - yield; - log "6"; + log_err "4"; + yield(); + log_err "5"; + yield(); + log_err "6"; } - diff --git a/src/test/run-pass/yield1.rs b/src/test/run-pass/yield1.rs new file mode 100644 index 00000000000..4c09b374202 --- /dev/null +++ b/src/test/run-pass/yield1.rs @@ -0,0 +1,17 @@ +// xfail-stage0 +// -*- rust -*- + +use std; + +import std::task::*; + +fn main() { + auto other = spawn child(); + log_err "1"; + yield(); + join(other); +} + +fn child() { + log_err "2"; +} diff --git a/src/test/run-pass/yield2.rs b/src/test/run-pass/yield2.rs index 4c824d6de7f..b4a2b270232 100644 --- a/src/test/run-pass/yield2.rs +++ b/src/test/run-pass/yield2.rs @@ -1,13 +1,13 @@ // xfail-stage0 -// xfail-stage1 -// xfail-stage2 // -*- rust -*- +use std; + fn main() { let int i = 0; while (i < 100) { i = i + 1; - log i; - yield; + log_err i; + std::task::yield(); } }