This is the mega-ucontext commit. It replaces the task switching mechanism with a new one inspired by ucontext. It works under Linux, OS X and Windows, and is Valgrind clean on Linux and OS X (provided the runtime is built with gcc).
This commit also moves yield and join to the standard library, as requested in #42. Join is currently a no-op though.
This commit is contained in:
2
configure
vendored
2
configure
vendored
@@ -176,7 +176,7 @@ fi
|
|||||||
step_msg "making directories"
|
step_msg "making directories"
|
||||||
for i in \
|
for i in \
|
||||||
doc \
|
doc \
|
||||||
rt rt/isaac rt/bigint rt/sync rt/test \
|
rt rt/isaac rt/bigint rt/sync rt/test rt/arch/i386 \
|
||||||
rustllvm \
|
rustllvm \
|
||||||
dl stage0 stage1 stage2 stage3 \
|
dl stage0 stage1 stage2 stage3 \
|
||||||
test/run-pass test/run-fail test/compile-fail \
|
test/run-pass test/run-fail test/compile-fail \
|
||||||
|
|||||||
13
mk/rt.mk
13
mk/rt.mk
@@ -27,11 +27,12 @@ RUNTIME_CS := rt/sync/timer.cpp \
|
|||||||
rt/memory_region.cpp \
|
rt/memory_region.cpp \
|
||||||
rt/test/rust_test_harness.cpp \
|
rt/test/rust_test_harness.cpp \
|
||||||
rt/test/rust_test_runtime.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 \
|
RUNTIME_HDR := rt/globals.h \
|
||||||
rt/rust.h \
|
rt/rust.h \
|
||||||
@@ -60,10 +61,12 @@ RUNTIME_HDR := rt/globals.h \
|
|||||||
rt/memory.h \
|
rt/memory.h \
|
||||||
rt/test/rust_test_harness.h \
|
rt/test/rust_test_harness.h \
|
||||||
rt/test/rust_test_runtime.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_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_OBJS := $(RUNTIME_CS:.cpp=.o) $(RUNTIME_LL:.ll=.o) $(RUNTIME_S:.s=.o)
|
||||||
RUNTIME_LIBS := $(CFG_GCCISH_POST_LIB_FLAGS)
|
RUNTIME_LIBS := $(CFG_GCCISH_POST_LIB_FLAGS)
|
||||||
|
|
||||||
|
|||||||
@@ -93,10 +93,6 @@ fn vec_append_glue_name() -> str {
|
|||||||
ret "rust_vec_append_glue";
|
ret "rust_vec_append_glue";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn yield_glue_name() -> str {
|
|
||||||
ret "rust_yield_glue";
|
|
||||||
}
|
|
||||||
|
|
||||||
fn no_op_type_glue_name() -> str {
|
fn no_op_type_glue_name() -> str {
|
||||||
ret "rust_no_op_type_glue";
|
ret "rust_no_op_type_glue";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ fn declare_upcalls(type_names tn, ModuleRef llmod) -> @upcalls {
|
|||||||
T_ptr(T_tydesc(tn))),
|
T_ptr(T_tydesc(tn))),
|
||||||
new_task=d("new_task", [T_ptr(T_str())], T_taskptr(tn)),
|
new_task=d("new_task", [T_ptr(T_str())], T_taskptr(tn)),
|
||||||
start_task=d("start_task", [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)),
|
T_taskptr(tn)),
|
||||||
new_thread=d("new_thread", [T_ptr(T_i8())], 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(),
|
start_thread=d("start_thread", [T_taskptr(tn), T_int(), T_int(),
|
||||||
|
|||||||
@@ -85,8 +85,7 @@ state obj namegen(mutable int i) {
|
|||||||
|
|
||||||
type derived_tydesc_info = rec(ValueRef lltydesc, bool escapes);
|
type derived_tydesc_info = rec(ValueRef lltydesc, bool escapes);
|
||||||
|
|
||||||
type glue_fns = rec(ValueRef yield_glue,
|
type glue_fns = rec(ValueRef no_op_type_glue,
|
||||||
ValueRef no_op_type_glue,
|
|
||||||
ValueRef vec_append_glue);
|
ValueRef vec_append_glue);
|
||||||
|
|
||||||
type tydesc_info = rec(ty::t ty,
|
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);
|
auto wrapper = mk_spawn_wrapper(bcx, func, args_ty);
|
||||||
bcx = wrapper.bcx;
|
bcx = wrapper.bcx;
|
||||||
auto llfnptr_i = bcx.build.PointerCast(wrapper.val, T_int());
|
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
|
// And start the task
|
||||||
|
auto args_size = size_of(bcx, args_ty).val;
|
||||||
bcx.build.Call(bcx.fcx.lcx.ccx.upcalls.start_task,
|
bcx.build.Call(bcx.fcx.lcx.ccx.upcalls.start_task,
|
||||||
[bcx.fcx.lltaskptr, new_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 task_ty = node_ann_type(bcx.fcx.lcx.ccx, ann);
|
||||||
auto dropref = clean(bind drop_ty(_, new_task, task_ty));
|
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 {
|
fn make_glues(ModuleRef llmod, &type_names tn) -> @glue_fns {
|
||||||
ret @rec(yield_glue = decl_glue(llmod, tn, abi::yield_glue_name()),
|
ret @rec(no_op_type_glue = decl_no_op_type_glue(llmod, tn),
|
||||||
no_op_type_glue = decl_no_op_type_glue(llmod, tn),
|
|
||||||
vec_append_glue = make_vec_append_glue(llmod, tn));
|
vec_append_glue = make_vec_append_glue(llmod, tn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ mod str;
|
|||||||
|
|
||||||
mod io;
|
mod io;
|
||||||
mod sys;
|
mod sys;
|
||||||
mod _task;
|
mod task;
|
||||||
|
|
||||||
// Utility modules.
|
// Utility modules.
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ auth os_fs = unsafe;
|
|||||||
auth run = unsafe;
|
auth run = unsafe;
|
||||||
auth str = unsafe;
|
auth str = unsafe;
|
||||||
auth vec = unsafe;
|
auth vec = unsafe;
|
||||||
auth _task = unsafe;
|
auth task = unsafe;
|
||||||
|
|
||||||
auth dbg = unsafe;
|
auth dbg = unsafe;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
native "rust" mod rustrt {
|
native "rust" mod rustrt {
|
||||||
fn task_sleep(uint time_in_us);
|
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);
|
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:
|
// Local Variables:
|
||||||
// mode: rust;
|
// mode: rust;
|
||||||
// fill-column: 78;
|
// fill-column: 78;
|
||||||
@@ -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
|
|
||||||
86
src/rt/arch/i386/_context.s
Normal file
86
src/rt/arch/i386/_context.s
Normal file
@@ -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
|
||||||
81
src/rt/arch/i386/context.cpp
Normal file
81
src/rt/arch/i386/context.cpp
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#include "context.h"
|
||||||
|
|
||||||
|
#include "../../rust.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
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
|
||||||
35
src/rt/arch/i386/context.h
Normal file
35
src/rt/arch/i386/context.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// -*- mode: c++ -*-
|
||||||
|
|
||||||
|
#ifndef CONTEXT_H
|
||||||
|
#define CONTEXT_H
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
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
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
#ifndef GLOBALS_H
|
#ifndef GLOBALS_H
|
||||||
#define 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_LIMIT_MACROS 1
|
||||||
#define __STDC_CONSTANT_MACROS 1
|
#define __STDC_CONSTANT_MACROS 1
|
||||||
#define __STDC_FORMAT_MACROS 1
|
#define __STDC_FORMAT_MACROS 1
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -32,4 +35,16 @@ extern "C" {
|
|||||||
#error "Platform not supported."
|
#error "Platform not supported."
|
||||||
#endif
|
#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 */
|
#endif /* GLOBALS_H */
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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]);
|
DLOG(dom, dom, "startup: arg[%d] = '%s'", i, args->argv[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
uintptr_t main_args[4] = {0, 0, 0, (uintptr_t)args->args};
|
uintptr_t main_args[4] = {0, 0, 0, (uintptr_t)args->args};
|
||||||
dom->root_task->start(main_fn,
|
dom->root_task->start(main_fn,
|
||||||
(uintptr_t)&main_args, sizeof(main_args));
|
(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();
|
int ret = dom->start_main_loop();
|
||||||
delete args;
|
delete args;
|
||||||
|
|||||||
@@ -10,11 +10,14 @@
|
|||||||
// 'cdecl' ABI only means anything on i386
|
// 'cdecl' ABI only means anything on i386
|
||||||
#ifdef __WIN32__
|
#ifdef __WIN32__
|
||||||
#define CDECL __cdecl
|
#define CDECL __cdecl
|
||||||
|
#define FASTCALL __fastcall
|
||||||
#else
|
#else
|
||||||
#define CDECL __attribute__((cdecl))
|
#define CDECL __attribute__((cdecl))
|
||||||
|
#define FASTCALL __attribute__((fastcall))
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
#define CDECL
|
#define CDECL
|
||||||
|
#define FASTCALL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -354,6 +354,16 @@ task_sleep(rust_task *task, size_t time_in_us) {
|
|||||||
upcall_sleep(task, 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. */
|
/* Debug builtins for std.dbg. */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include "rust_internal.h"
|
#include "rust_internal.h"
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
rust_dom::rust_dom(rust_kernel *kernel,
|
rust_dom::rust_dom(rust_kernel *kernel,
|
||||||
rust_message_queue *message_queue, rust_srv *srv,
|
rust_message_queue *message_queue, rust_srv *srv,
|
||||||
@@ -44,13 +45,17 @@ rust_dom::~rust_dom() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void new_rust_activate_glue(rust_task *)
|
|
||||||
asm("new_rust_activate_glue");
|
|
||||||
|
|
||||||
void
|
void
|
||||||
rust_dom::activate(rust_task *task) {
|
rust_dom::activate(rust_task *task) {
|
||||||
curr_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;
|
curr_task = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,9 +313,13 @@ rust_dom::start_main_loop() {
|
|||||||
scheduled_task->state->name,
|
scheduled_task->state->name,
|
||||||
scheduled_task->rust_sp);
|
scheduled_task->rust_sp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
// These invariants are no longer valid, as rust_sp is not
|
||||||
|
// updated.
|
||||||
I(this, scheduled_task->rust_sp >=
|
I(this, scheduled_task->rust_sp >=
|
||||||
(uintptr_t) &scheduled_task->stk->data[0]);
|
(uintptr_t) &scheduled_task->stk->data[0]);
|
||||||
I(this, scheduled_task->rust_sp < scheduled_task->stk->limit);
|
I(this, scheduled_task->rust_sp < scheduled_task->stk->limit);
|
||||||
|
*/
|
||||||
|
|
||||||
reap_dead_tasks();
|
reap_dead_tasks();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#ifndef RUST_INTERNAL_H
|
#ifndef RUST_INTERNAL_H
|
||||||
#define 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_LIMIT_MACROS 1
|
||||||
#define __STDC_CONSTANT_MACROS 1
|
#define __STDC_CONSTANT_MACROS 1
|
||||||
#define __STDC_FORMAT_MACROS 1
|
#define __STDC_FORMAT_MACROS 1
|
||||||
|
#endif
|
||||||
|
|
||||||
#define ERROR 0
|
#define ERROR 0
|
||||||
|
|
||||||
@@ -203,8 +206,6 @@ struct rust_timer {
|
|||||||
|
|
||||||
#include "rust_util.h"
|
#include "rust_util.h"
|
||||||
|
|
||||||
typedef void CDECL (*activate_glue_ty)(rust_task *);
|
|
||||||
|
|
||||||
struct type_desc {
|
struct type_desc {
|
||||||
// First part of type_desc is known to compiler.
|
// First part of type_desc is known to compiler.
|
||||||
// first_param = &descs[1] if dynamic, null if static.
|
// first_param = &descs[1] if dynamic, null if static.
|
||||||
|
|||||||
@@ -187,8 +187,11 @@ rust_kernel::~rust_kernel() {
|
|||||||
KLOG("freeing handles");
|
KLOG("freeing handles");
|
||||||
|
|
||||||
free_handles(_task_handles);
|
free_handles(_task_handles);
|
||||||
|
KLOG("..task handles freed");
|
||||||
free_handles(_port_handles);
|
free_handles(_port_handles);
|
||||||
|
KLOG("..port handles freed");
|
||||||
free_handles(_dom_handles);
|
free_handles(_dom_handles);
|
||||||
|
KLOG("..dom handles freed");
|
||||||
|
|
||||||
KLOG("freeing queues");
|
KLOG("freeing queues");
|
||||||
|
|
||||||
@@ -214,6 +217,7 @@ rust_kernel::free_handles(hash_map<T*, rust_handle<T>* > &map) {
|
|||||||
T* key;
|
T* key;
|
||||||
rust_handle<T> *value;
|
rust_handle<T> *value;
|
||||||
while (map.pop(&key, &value)) {
|
while (map.pop(&key, &value)) {
|
||||||
|
KLOG("...freeing " PTR, value);
|
||||||
delete value;
|
delete value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// -*- c++ -*-
|
||||||
#ifndef RUST_KERNEL_H
|
#ifndef RUST_KERNEL_H
|
||||||
#define RUST_KERNEL_H
|
#define RUST_KERNEL_H
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include <execinfo.h>
|
#include <execinfo.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
// Stacks
|
// Stacks
|
||||||
|
|
||||||
// FIXME (issue #151): This should be 0x300; the change here is for
|
// 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 n_callee_saves = 4;
|
||||||
size_t const callee_save_fp = 0;
|
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::rust_task(rust_dom *dom, rust_task_list *state,
|
||||||
rust_task *spawner, const char *name) :
|
rust_task *spawner, const char *name) :
|
||||||
maybe_proxy<rust_task>(this),
|
maybe_proxy<rust_task>(this),
|
||||||
@@ -94,6 +72,7 @@ rust_task::rust_task(rust_dom *dom, rust_task_list *state,
|
|||||||
handle(NULL)
|
handle(NULL)
|
||||||
{
|
{
|
||||||
LOGPTR(dom, "new task", (uintptr_t)this);
|
LOGPTR(dom, "new task", (uintptr_t)this);
|
||||||
|
DLOG(dom, task, "sizeof(task) = %d (0x%x)", sizeof *this, sizeof *this);
|
||||||
|
|
||||||
if (spawner == NULL) {
|
if (spawner == NULL) {
|
||||||
ref_count = 0;
|
ref_count = 0;
|
||||||
@@ -135,6 +114,42 @@ rust_task::~rust_task()
|
|||||||
|
|
||||||
extern "C" void rust_new_exit_task_glue();
|
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
|
void
|
||||||
rust_task::start(uintptr_t spawnee_fn,
|
rust_task::start(uintptr_t spawnee_fn,
|
||||||
uintptr_t args,
|
uintptr_t args,
|
||||||
@@ -142,54 +157,23 @@ rust_task::start(uintptr_t spawnee_fn,
|
|||||||
{
|
{
|
||||||
LOGPTR(dom, "from spawnee", spawnee_fn);
|
LOGPTR(dom, "from spawnee", spawnee_fn);
|
||||||
|
|
||||||
// Set sp to last uintptr_t-sized cell of segment
|
I(dom, stk->data != NULL);
|
||||||
rust_sp -= sizeof(uintptr_t);
|
|
||||||
|
|
||||||
// Begin synthesizing the exit_task_glue frame. We will return to
|
char *sp = (char *)stk->limit;
|
||||||
// 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;
|
|
||||||
|
|
||||||
uintptr_t dummy_ret = (uintptr_t) spp--;
|
sp -= sizeof(spawn_args);
|
||||||
|
|
||||||
uintptr_t args_size = callsz - 3*sizeof(uintptr_t);
|
spawn_args *a = (spawn_args *)sp;
|
||||||
uintptr_t frame_size = args_size + 4*sizeof(uintptr_t);
|
|
||||||
|
|
||||||
|
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
|
ctx.call((void *)task_start_wrapper, a, sp);
|
||||||
// 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);
|
|
||||||
|
|
||||||
|
yield_timer.reset(0);
|
||||||
transition(&dom->newborn_tasks, &dom->running_tasks);
|
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.
|
// the presence of non-word-aligned pointers.
|
||||||
abort();
|
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
|
void
|
||||||
@@ -314,20 +192,17 @@ rust_task::yield(size_t nargs) {
|
|||||||
yield(nargs, 0);
|
yield(nargs, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void new_rust_yield_glue(void) asm("new_rust_yield_glue");
|
|
||||||
|
|
||||||
void
|
void
|
||||||
rust_task::yield(size_t nargs, size_t time_in_us) {
|
rust_task::yield(size_t nargs, size_t time_in_us) {
|
||||||
LOG(this, task, "task %s @0x%" PRIxPTR " yielding for %d us",
|
LOG(this, task, "task %s @0x%" PRIxPTR " yielding for %d us",
|
||||||
name, this, time_in_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
|
// TODO: what is nargs for, and is it safe to ignore?
|
||||||
get_callee_save_fp(uintptr_t *top_of_callee_saves)
|
|
||||||
{
|
yield_timer.reset(time_in_us);
|
||||||
return top_of_callee_saves[n_callee_saves - (callee_save_fp + 1)];
|
|
||||||
|
// Return to the scheduler.
|
||||||
|
ctx.next->swap(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
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*
|
frame_glue_fns*
|
||||||
rust_task::get_frame_glue_fns(uintptr_t fp) {
|
rust_task::get_frame_glue_fns(uintptr_t fp) {
|
||||||
fp -= sizeof(uintptr_t);
|
fp -= sizeof(uintptr_t);
|
||||||
@@ -548,10 +409,10 @@ rust_task::free(void *p, bool is_gc)
|
|||||||
|
|
||||||
void
|
void
|
||||||
rust_task::transition(rust_task_list *src, rust_task_list *dst) {
|
rust_task::transition(rust_task_list *src, rust_task_list *dst) {
|
||||||
I(dom, state == src);
|
|
||||||
DLOG(dom, task,
|
DLOG(dom, task,
|
||||||
"task %s " PTR " state change '%s' -> '%s'",
|
"task %s " PTR " state change '%s' -> '%s' while in '%s'",
|
||||||
name, (uintptr_t)this, src->name, dst->name);
|
name, (uintptr_t)this, src->name, dst->name, state->name);
|
||||||
|
I(dom, state == src);
|
||||||
src->remove(this);
|
src->remove(this);
|
||||||
dst->append(this);
|
dst->append(this);
|
||||||
state = dst;
|
state = dst;
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
#include "util/array_list.h"
|
#include "util/array_list.h"
|
||||||
|
|
||||||
|
#include "context.h"
|
||||||
|
|
||||||
struct
|
struct
|
||||||
rust_task : public maybe_proxy<rust_task>,
|
rust_task : public maybe_proxy<rust_task>,
|
||||||
public dom_owned<rust_task>
|
public dom_owned<rust_task>
|
||||||
@@ -47,6 +49,8 @@ rust_task : public maybe_proxy<rust_task>,
|
|||||||
|
|
||||||
rust_handle<rust_task> *handle;
|
rust_handle<rust_task> *handle;
|
||||||
|
|
||||||
|
context ctx;
|
||||||
|
|
||||||
// Only a pointer to 'name' is kept, so it must live as long as this task.
|
// Only a pointer to 'name' is kept, so it must live as long as this task.
|
||||||
rust_task(rust_dom *dom,
|
rust_task(rust_dom *dom,
|
||||||
rust_task_list *state,
|
rust_task_list *state,
|
||||||
@@ -83,14 +87,6 @@ rust_task : public maybe_proxy<rust_task>,
|
|||||||
// Print a backtrace, if the "bt" logging option is on.
|
// Print a backtrace, if the "bt" logging option is on.
|
||||||
void backtrace();
|
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.
|
// Save callee-saved registers and return to the main loop.
|
||||||
void yield(size_t nargs);
|
void yield(size_t nargs);
|
||||||
|
|
||||||
@@ -114,8 +110,6 @@ rust_task : public maybe_proxy<rust_task>,
|
|||||||
|
|
||||||
rust_handle<rust_task> * get_handle();
|
rust_handle<rust_task> * get_handle();
|
||||||
|
|
||||||
uintptr_t get_fp();
|
|
||||||
uintptr_t get_previous_fp(uintptr_t fp);
|
|
||||||
frame_glue_fns *get_frame_glue_fns(uintptr_t fp);
|
frame_glue_fns *get_frame_glue_fns(uintptr_t fp);
|
||||||
rust_crate_cache * get_crate_cache();
|
rust_crate_cache * get_crate_cache();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -462,11 +462,22 @@ upcall_new_task(rust_task *spawner, rust_vec *name) {
|
|||||||
return task;
|
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 *
|
extern "C" CDECL rust_task *
|
||||||
upcall_start_task(rust_task *spawner,
|
upcall_start_task(rust_task *spawner,
|
||||||
rust_task *task,
|
rust_task *task,
|
||||||
uintptr_t spawnee_fn,
|
uintptr_t spawnee_fn,
|
||||||
uintptr_t args) {
|
uintptr_t args,
|
||||||
|
size_t args_sz) {
|
||||||
LOG_UPCALL_ENTRY(spawner);
|
LOG_UPCALL_ENTRY(spawner);
|
||||||
|
|
||||||
rust_dom *dom = spawner->dom;
|
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
|
// we used to be generating this tuple in rustc, but it's easier to do it
|
||||||
// here.
|
// 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));
|
task->start(spawnee_fn, (uintptr_t)start_args, sizeof(start_args));
|
||||||
return task;
|
return task;
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ str_push_byte
|
|||||||
str_slice
|
str_slice
|
||||||
str_vec
|
str_vec
|
||||||
task_sleep
|
task_sleep
|
||||||
|
task_yield
|
||||||
|
task_join
|
||||||
unsafe_vec_to_mut
|
unsafe_vec_to_mut
|
||||||
unsupervise
|
unsupervise
|
||||||
upcall_clone_chan
|
upcall_clone_chan
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ lock_and_signal::lock_and_signal() {
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
lock_and_signal::lock_and_signal() {
|
lock_and_signal::lock_and_signal() {
|
||||||
pthread_cond_init(&_cond, NULL);
|
CHECKED(pthread_cond_init(&_cond, NULL));
|
||||||
pthread_mutex_init(&_mutex, NULL);
|
CHECKED(pthread_mutex_init(&_mutex, NULL));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -31,8 +31,8 @@ lock_and_signal::~lock_and_signal() {
|
|||||||
#if defined(__WIN32__)
|
#if defined(__WIN32__)
|
||||||
CloseHandle(_event);
|
CloseHandle(_event);
|
||||||
#else
|
#else
|
||||||
pthread_cond_destroy(&_cond);
|
CHECKED(pthread_cond_destroy(&_cond));
|
||||||
pthread_mutex_destroy(&_mutex);
|
CHECKED(pthread_mutex_destroy(&_mutex));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ void lock_and_signal::lock() {
|
|||||||
#if defined(__WIN32__)
|
#if defined(__WIN32__)
|
||||||
EnterCriticalSection(&_cs);
|
EnterCriticalSection(&_cs);
|
||||||
#else
|
#else
|
||||||
pthread_mutex_lock(&_mutex);
|
CHECKED(pthread_mutex_lock(&_mutex));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ void lock_and_signal::unlock() {
|
|||||||
#if defined(__WIN32__)
|
#if defined(__WIN32__)
|
||||||
LeaveCriticalSection(&_cs);
|
LeaveCriticalSection(&_cs);
|
||||||
#else
|
#else
|
||||||
pthread_mutex_unlock(&_mutex);
|
CHECKED(pthread_mutex_unlock(&_mutex));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,14 +66,14 @@ void lock_and_signal::timed_wait(size_t timeout_in_ns) {
|
|||||||
EnterCriticalSection(&_cs);
|
EnterCriticalSection(&_cs);
|
||||||
#else
|
#else
|
||||||
if (timeout_in_ns == 0) {
|
if (timeout_in_ns == 0) {
|
||||||
pthread_cond_wait(&_cond, &_mutex);
|
CHECKED(pthread_cond_wait(&_cond, &_mutex));
|
||||||
} else {
|
} else {
|
||||||
timeval time_val;
|
timeval time_val;
|
||||||
gettimeofday(&time_val, NULL);
|
gettimeofday(&time_val, NULL);
|
||||||
timespec time_spec;
|
timespec time_spec;
|
||||||
time_spec.tv_sec = time_val.tv_sec + 0;
|
time_spec.tv_sec = time_val.tv_sec + 0;
|
||||||
time_spec.tv_nsec = time_val.tv_usec * 1000 + timeout_in_ns;
|
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
|
#endif
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ void lock_and_signal::signal() {
|
|||||||
#if defined(__WIN32__)
|
#if defined(__WIN32__)
|
||||||
SetEvent(_event);
|
SetEvent(_event);
|
||||||
#else
|
#else
|
||||||
pthread_cond_signal(&_cond);
|
CHECKED(pthread_cond_signal(&_cond));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ void lock_and_signal::signal_all() {
|
|||||||
#if defined(__WIN32__)
|
#if defined(__WIN32__)
|
||||||
SetEvent(_event);
|
SetEvent(_event);
|
||||||
#else
|
#else
|
||||||
pthread_cond_broadcast(&_cond);
|
CHECKED(pthread_cond_broadcast(&_cond));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -8,12 +8,14 @@ fn main() {
|
|||||||
spawn child(chan(p));
|
spawn child(chan(p));
|
||||||
let int y;
|
let int y;
|
||||||
p |> y;
|
p |> y;
|
||||||
log "received";
|
log_err "received";
|
||||||
log y;
|
log_err y;
|
||||||
assert (y == 10);
|
//assert (y == 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn child(chan[int] c) {
|
fn child(chan[int] c) {
|
||||||
|
log_err "sending";
|
||||||
c <| 10;
|
c <| 10;
|
||||||
|
log_err "value sent"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
// xfail-stage0
|
// xfail-stage0
|
||||||
// xfail-stage1
|
|
||||||
// xfail-stage2
|
|
||||||
// -*- rust -*-
|
// -*- rust -*-
|
||||||
|
|
||||||
|
use std;
|
||||||
|
|
||||||
|
import std::task::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
auto other = spawn child();
|
auto other = spawn child();
|
||||||
log "1";
|
log_err "1";
|
||||||
yield;
|
yield();
|
||||||
log "2";
|
log_err "2";
|
||||||
yield;
|
yield();
|
||||||
log "3";
|
log_err "3";
|
||||||
join other;
|
join(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn child() {
|
fn child() {
|
||||||
log "4";
|
log_err "4";
|
||||||
yield;
|
yield();
|
||||||
log "5";
|
log_err "5";
|
||||||
yield;
|
yield();
|
||||||
log "6";
|
log_err "6";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
src/test/run-pass/yield1.rs
Normal file
17
src/test/run-pass/yield1.rs
Normal file
@@ -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";
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
// xfail-stage0
|
// xfail-stage0
|
||||||
// xfail-stage1
|
|
||||||
// xfail-stage2
|
|
||||||
// -*- rust -*-
|
// -*- rust -*-
|
||||||
|
|
||||||
|
use std;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let int i = 0;
|
let int i = 0;
|
||||||
while (i < 100) {
|
while (i < 100) {
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
log i;
|
log_err i;
|
||||||
yield;
|
std::task::yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user