From d5e0622f95252e442b909011129336c11ddb0151 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Feb 2014 14:17:10 -0800 Subject: [PATCH 1/4] Fix a bug where cached stacks weren't re-used The condition was the wrong direction and it also didn't take equality into account. Tests were added for both cases. For the small benchmark of `task::try(proc() {}).unwrap()`, this takes the iteration time on OSX from 15119 ns/iter to 6179 ns/iter (timed with RUST_THREADS=1) cc #11389 --- src/libgreen/stack.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/libgreen/stack.rs b/src/libgreen/stack.rs index 4b3db5ef8ed90..8a5e6be17c87d 100644 --- a/src/libgreen/stack.rs +++ b/src/libgreen/stack.rs @@ -138,9 +138,9 @@ impl StackPool { pub fn take_stack(&mut self, min_size: uint) -> Stack { // Ideally this would be a binary search - match self.stacks.iter().position(|s| s.min_size < min_size) { + match self.stacks.iter().position(|s| min_size <= s.min_size) { Some(idx) => self.stacks.swap_remove(idx), - None => Stack::new(min_size) + None => Stack::new(min_size) } } @@ -156,3 +156,33 @@ extern { end: *libc::uintptr_t) -> libc::c_uint; fn rust_valgrind_stack_deregister(id: libc::c_uint); } + +#[cfg(test)] +mod tests { + use super::StackPool; + + #[test] + fn stack_pool_caches() { + let mut p = StackPool::new(); + let s = p.take_stack(10); + p.give_stack(s); + let s = p.take_stack(4); + assert_eq!(s.min_size, 10); + p.give_stack(s); + let s = p.take_stack(14); + assert_eq!(s.min_size, 14); + p.give_stack(s); + } + + #[test] + fn stack_pool_caches_exact() { + let mut p = StackPool::new(); + let mut s = p.take_stack(10); + s.valgrind_id = 100; + p.give_stack(s); + + let s = p.take_stack(10); + assert_eq!(s.min_size, 10); + assert_eq!(s.valgrind_id, 100); + } +} From aaead93c4554b685935b70565fc1bb54edd945d6 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Feb 2014 14:41:57 -0800 Subject: [PATCH 2/4] Don't allocate in LocalHeap::new() One of these is allocated for every task, trying to cut down on allocations cc #11389 --- src/libstd/rt/local_heap.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libstd/rt/local_heap.rs b/src/libstd/rt/local_heap.rs index 023f712d3a08a..7acce2ecb5ba9 100644 --- a/src/libstd/rt/local_heap.rs +++ b/src/libstd/rt/local_heap.rs @@ -23,6 +23,7 @@ use rt::local::Local; use rt::task::Task; use unstable::raw; use vec::ImmutableVector; +use vec_ng::Vec; // This has no meaning with out rtdebug also turned on. #[cfg(rtdebug)] @@ -33,7 +34,7 @@ static MAGIC: u32 = 0xbadc0ffe; pub type Box = raw::Box<()>; pub struct MemoryRegion { - priv allocations: ~[*AllocHeader], + priv allocations: Vec<*AllocHeader>, priv live_allocations: uint, } @@ -48,7 +49,7 @@ impl LocalHeap { #[inline] pub fn new() -> LocalHeap { let region = MemoryRegion { - allocations: ~[], + allocations: Vec::new(), live_allocations: 0, }; LocalHeap { @@ -248,8 +249,8 @@ impl MemoryRegion { fn release(&mut self, alloc: &AllocHeader) { alloc.assert_sane(); if TRACK_ALLOCATIONS > 1 { - rtassert!(self.allocations[alloc.index] == alloc as *AllocHeader); - self.allocations[alloc.index] = ptr::null(); + rtassert!(self.allocations.as_slice()[alloc.index] == alloc as *AllocHeader); + self.allocations.as_mut_slice()[alloc.index] = ptr::null(); } } #[cfg(not(rtdebug))] @@ -260,8 +261,8 @@ impl MemoryRegion { fn update(&mut self, alloc: &mut AllocHeader, orig: *AllocHeader) { alloc.assert_sane(); if TRACK_ALLOCATIONS > 1 { - rtassert!(self.allocations[alloc.index] == orig); - self.allocations[alloc.index] = &*alloc as *AllocHeader; + rtassert!(self.allocations.as_slice()[alloc.index] == orig); + self.allocations.as_mut_slice()[alloc.index] = &*alloc as *AllocHeader; } } #[cfg(not(rtdebug))] @@ -274,7 +275,7 @@ impl Drop for MemoryRegion { if self.live_allocations != 0 { rtabort!("leaked managed memory ({} objects)", self.live_allocations); } - rtassert!(self.allocations.iter().all(|s| s.is_null())); + rtassert!(self.allocations.as_slice().iter().all(|s| s.is_null())); } } From 21a064d5a340a00042c81745cc7d2a65691e84be Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Feb 2014 14:49:56 -0800 Subject: [PATCH 3/4] Don't require an allocation for on_exit messages Instead, use an enum to allow running both a procedure and sending the task result over a channel. I expect the common case to be sending on a channel (e.g. task::try), so don't require an extra allocation in the common case. cc #11389 --- src/libgreen/task.rs | 5 ++--- src/libnative/task.rs | 7 ++----- src/libstd/rt/task.rs | 16 ++++++++++++---- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/libgreen/task.rs b/src/libgreen/task.rs index e492acb4468fd..6d61f8e391900 100644 --- a/src/libgreen/task.rs +++ b/src/libgreen/task.rs @@ -22,7 +22,7 @@ use std::cast; use std::rt::Runtime; use std::rt::rtio; use std::rt::local::Local; -use std::rt::task::{Task, BlockedTask}; +use std::rt::task::{Task, BlockedTask, SendMessage}; use std::task::TaskOpts; use std::unstable::mutex::Mutex; @@ -131,8 +131,7 @@ impl GreenTask { task.stdout = stdout; match notify_chan { Some(chan) => { - let on_exit = proc(task_result) { chan.send(task_result) }; - task.death.on_exit = Some(on_exit); + task.death.on_exit = Some(SendMessage(chan)); } None => {} } diff --git a/src/libnative/task.rs b/src/libnative/task.rs index d0ca8364aa7d9..a9c3afbbb16c8 100644 --- a/src/libnative/task.rs +++ b/src/libnative/task.rs @@ -18,7 +18,7 @@ use std::cast; use std::rt::env; use std::rt::local::Local; use std::rt::rtio; -use std::rt::task::{Task, BlockedTask}; +use std::rt::task::{Task, BlockedTask, SendMessage}; use std::rt::thread::Thread; use std::rt; use std::task::TaskOpts; @@ -68,10 +68,7 @@ pub fn spawn_opts(opts: TaskOpts, f: proc()) { task.stderr = stderr; task.stdout = stdout; match notify_chan { - Some(chan) => { - let on_exit = proc(task_result) { chan.send(task_result) }; - task.death.on_exit = Some(on_exit); - } + Some(chan) => { task.death.on_exit = Some(SendMessage(chan)); } None => {} } diff --git a/src/libstd/rt/task.rs b/src/libstd/rt/task.rs index e2b94e655e870..0719523af775f 100644 --- a/src/libstd/rt/task.rs +++ b/src/libstd/rt/task.rs @@ -17,6 +17,7 @@ use any::AnyOwnExt; use cast; use cleanup; use clone::Clone; +use comm::Chan; use io::Writer; use iter::{Iterator, Take}; use local_data; @@ -67,11 +68,17 @@ pub enum BlockedTask { Shared(UnsafeArc), } +pub enum DeathAction { + /// Action to be done with the exit code. If set, also makes the task wait + /// until all its watched children exit before collecting the status. + Execute(proc(TaskResult)), + /// A channel to send the result of the task on when the task exits + SendMessage(Chan), +} + /// Per-task state related to task death, killing, failure, etc. pub struct Death { - // Action to be done with the exit code. If set, also makes the task wait - // until all its watched children exit before collecting the status. - on_exit: Option, + on_exit: Option, } pub struct BlockedTasks { @@ -381,7 +388,8 @@ impl Death { /// Collect failure exit codes from children and propagate them to a parent. pub fn collect_failure(&mut self, result: TaskResult) { match self.on_exit.take() { - Some(f) => f(result), + Some(Execute(f)) => f(result), + Some(SendMessage(ch)) => { ch.try_send(result); } None => {} } } From 301ff0c2df3d26a5b287ab61d80f5ca7845e827b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Feb 2014 16:13:50 -0800 Subject: [PATCH 4/4] Remove two allocations from spawning a green task Two unfortunate allocations were wrapping a proc() in a proc() with GreenTask::build_start_wrapper, and then boxing this proc in a ~proc() inside of Context::new(). Both of these allocations were a direct result from two conditions: 1. The Context::new() function has a nice api of taking a procedure argument to start up a new context with. This inherently required an allocation by build_start_wrapper because extra code needed to be run around the edges of a user-provided proc() for a new task. 2. The initial bootstrap code only understood how to pass one argument to the next function. By modifying the assembly and entry points to understand more than one argument, more information is passed through in registers instead of allocating a pointer-sized context. This is sadly where I end up throwing mips under a bus because I have no idea what's going on in the mips context switching code and don't know how to modify it. Closes #7767 cc #11389 --- mk/crates.mk | 2 +- mk/rt.mk | 5 +- src/libgreen/context.rs | 113 ++++++++++++++++++---------------- src/libgreen/coroutine.rs | 18 ------ src/libgreen/sched.rs | 2 +- src/libgreen/task.rs | 108 ++++++++++++++++++-------------- src/libstd/unstable/raw.rs | 6 ++ src/rt/arch/arm/_context.S | 8 +++ src/rt/arch/x86_64/_context.S | 33 ++++++++++ 9 files changed, 175 insertions(+), 120 deletions(-) diff --git a/mk/crates.mk b/mk/crates.mk index d7365a827b7a2..80231ad2ba4bc 100644 --- a/mk/crates.mk +++ b/mk/crates.mk @@ -57,7 +57,7 @@ TOOLS := compiletest rustdoc rustc DEPS_std := native:rustrt native:compiler-rt DEPS_extra := std term sync serialize getopts collections -DEPS_green := std +DEPS_green := std native:context_switch DEPS_rustuv := std native:uv native:uv_support DEPS_native := std DEPS_syntax := std extra term serialize collections diff --git a/mk/rt.mk b/mk/rt.mk index ebb1f83398ecb..10b73c6b39533 100644 --- a/mk/rt.mk +++ b/mk/rt.mk @@ -35,7 +35,7 @@ # that's per-target so you're allowed to conditionally add files based on the # target. ################################################################################ -NATIVE_LIBS := rustrt sundown uv_support morestack miniz +NATIVE_LIBS := rustrt sundown uv_support morestack miniz context_switch # $(1) is the target triple define NATIVE_LIBRARIES @@ -54,9 +54,10 @@ NATIVE_DEPS_rustrt_$(1) := rust_builtin.c \ rust_android_dummy.c \ rust_test_helpers.c \ rust_try.ll \ - arch/$$(HOST_$(1))/_context.S \ arch/$$(HOST_$(1))/record_sp.S NATIVE_DEPS_morestack_$(1) := arch/$$(HOST_$(1))/morestack.S +NATIVE_DEPS_context_switch_$(1) := \ + arch/$$(HOST_$(1))/_context.S ################################################################################ # You shouldn't find it that necessary to edit anything below this line. diff --git a/src/libgreen/context.rs b/src/libgreen/context.rs index 4e626b3bec778..58188ede13cb6 100644 --- a/src/libgreen/context.rs +++ b/src/libgreen/context.rs @@ -8,12 +8,12 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::libc::c_void; use std::uint; use std::cast::{transmute, transmute_mut_unsafe, transmute_region, transmute_mut_region}; use stack::Stack; use std::unstable::stack; +use std::unstable::raw; // FIXME #7761: Registers is boxed so that it is 16-byte aligned, for storing // SSE regs. It would be marginally better not to do this. In C++ we @@ -22,47 +22,33 @@ use std::unstable::stack; // the registers are sometimes empty, but the discriminant would // then misalign the regs again. pub struct Context { - /// The context entry point, saved here for later destruction - priv start: Option<~proc()>, /// Hold the registers while the task or scheduler is suspended priv regs: ~Registers, /// Lower bound and upper bound for the stack priv stack_bounds: Option<(uint, uint)>, } +pub type InitFn = extern "C" fn(uint, *(), *()) -> !; + impl Context { pub fn empty() -> Context { Context { - start: None, regs: new_regs(), stack_bounds: None, } } /// Create a new context that will resume execution by running proc() - pub fn new(start: proc(), stack: &mut Stack) -> Context { - // The C-ABI function that is the task entry point - // - // Note that this function is a little sketchy. We're taking a - // procedure, transmuting it to a stack-closure, and then calling to - // closure. This leverages the fact that the representation of these two - // types is the same. - // - // The reason that we're doing this is that this procedure is expected - // to never return. The codegen which frees the environment of the - // procedure occurs *after* the procedure has completed, and this means - // that we'll never actually free the procedure. - // - // To solve this, we use this transmute (to not trigger the procedure - // deallocation here), and then store a copy of the procedure in the - // `Context` structure returned. When the `Context` is deallocated, then - // the entire procedure box will be deallocated as well. - extern fn task_start_wrapper(f: &proc()) { - unsafe { - let f: &|| = transmute(f); - (*f)() - } - } + /// + /// The `init` function will be run with `arg` and the `start` procedure + /// split up into code and env pointers. It is required that the `init` + /// function never return. + /// + /// FIXME: this is basically an awful the interface. The main reason for + /// this is to reduce the number of allocations made when a green + /// task is spawned as much as possible + pub fn new(init: InitFn, arg: uint, start: proc(), + stack: &mut Stack) -> Context { let sp: *uint = stack.end(); let sp: *mut uint = unsafe { transmute_mut_unsafe(sp) }; @@ -74,14 +60,10 @@ impl Context { transmute_region(&*regs)); }; - // FIXME #7767: Putting main into a ~ so it's a thin pointer and can - // be passed to the spawn function. Another unfortunate - // allocation - let start = ~start; - initialize_call_frame(&mut *regs, - task_start_wrapper as *c_void, - unsafe { transmute(&*start) }, + init, + arg, + unsafe { transmute(start) }, sp); // Scheduler tasks don't have a stack in the "we allocated it" sense, @@ -96,7 +78,6 @@ impl Context { Some((stack_base as uint, sp as uint)) }; return Context { - start: Some(start), regs: regs, stack_bounds: bounds, } @@ -138,7 +119,7 @@ impl Context { } } -#[link(name = "rustrt", kind = "static")] +#[link(name = "context_switch", kind = "static")] extern { fn rust_swap_registers(out_regs: *mut Registers, in_regs: *Registers); } @@ -185,13 +166,17 @@ fn new_regs() -> ~Registers { } #[cfg(target_arch = "x86")] -fn initialize_call_frame(regs: &mut Registers, fptr: *c_void, arg: *c_void, - sp: *mut uint) { +fn initialize_call_frame(regs: &mut Registers, fptr: InitFn, arg: uint, + procedure: raw::Procedure, sp: *mut uint) { + // x86 has interesting stack alignment requirements, so do some alignment + // plus some offsetting to figure out what the actual stack should be. let sp = align_down(sp); let sp = mut_offset(sp, -4); - unsafe { *sp = arg as uint }; + unsafe { *mut_offset(sp, 2) = procedure.env as uint }; + unsafe { *mut_offset(sp, 1) = procedure.code as uint }; + unsafe { *mut_offset(sp, 0) = arg as uint }; let sp = mut_offset(sp, -1); unsafe { *sp = 0 }; // The final return address @@ -215,14 +200,18 @@ fn new_regs() -> ~Registers { ~([0, .. 34]) } fn new_regs() -> ~Registers { ~([0, .. 22]) } #[cfg(target_arch = "x86_64")] -fn initialize_call_frame(regs: &mut Registers, fptr: *c_void, arg: *c_void, - sp: *mut uint) { +fn initialize_call_frame(regs: &mut Registers, fptr: InitFn, arg: uint, + procedure: raw::Procedure, sp: *mut uint) { + extern { fn rust_bootstrap_green_task(); } // Redefinitions from rt/arch/x86_64/regs.h - static RUSTRT_ARG0: uint = 3; static RUSTRT_RSP: uint = 1; static RUSTRT_IP: uint = 8; static RUSTRT_RBP: uint = 2; + static RUSTRT_R12: uint = 4; + static RUSTRT_R13: uint = 5; + static RUSTRT_R14: uint = 6; + static RUSTRT_R15: uint = 7; let sp = align_down(sp); let sp = mut_offset(sp, -1); @@ -231,13 +220,23 @@ fn initialize_call_frame(regs: &mut Registers, fptr: *c_void, arg: *c_void, unsafe { *sp = 0; } rtdebug!("creating call frame"); - rtdebug!("fptr {}", fptr); - rtdebug!("arg {}", arg); + rtdebug!("fptr {:#x}", fptr as uint); + rtdebug!("arg {:#x}", arg); rtdebug!("sp {}", sp); - regs[RUSTRT_ARG0] = arg as uint; + // These registers are frobbed by rust_bootstrap_green_task into the right + // location so we can invoke the "real init function", `fptr`. + regs[RUSTRT_R12] = arg as uint; + regs[RUSTRT_R13] = procedure.code as uint; + regs[RUSTRT_R14] = procedure.env as uint; + regs[RUSTRT_R15] = fptr as uint; + + // These registers are picked up by the regulard context switch paths. These + // will put us in "mostly the right context" except for frobbing all the + // arguments to the right place. We have the small trampoline code inside of + // rust_bootstrap_green_task to do that. regs[RUSTRT_RSP] = sp as uint; - regs[RUSTRT_IP] = fptr as uint; + regs[RUSTRT_IP] = rust_bootstrap_green_task as uint; // Last base pointer on the stack should be 0 regs[RUSTRT_RBP] = 0; @@ -250,8 +249,10 @@ type Registers = [uint, ..32]; fn new_regs() -> ~Registers { ~([0, .. 32]) } #[cfg(target_arch = "arm")] -fn initialize_call_frame(regs: &mut Registers, fptr: *c_void, arg: *c_void, - sp: *mut uint) { +fn initialize_call_frame(regs: &mut Registers, fptr: InitFn, arg: uint, + procedure: raw::Procedure, sp: *mut uint) { + extern { fn rust_bootstrap_green_task(); } + let sp = align_down(sp); // sp of arm eabi is 8-byte aligned let sp = mut_offset(sp, -2); @@ -259,9 +260,15 @@ fn initialize_call_frame(regs: &mut Registers, fptr: *c_void, arg: *c_void, // The final return address. 0 indicates the bottom of the stack unsafe { *sp = 0; } - regs[0] = arg as uint; // r0 - regs[13] = sp as uint; // #53 sp, r13 - regs[14] = fptr as uint; // #60 pc, r15 --> lr + // ARM uses the same technique as x86_64 to have a landing pad for the start + // of all new green tasks. Neither r1/r2 are saved on a context switch, so + // the shim will copy r3/r4 into r1/r2 and then execute the function in r5 + regs[0] = arg as uint; // r0 + regs[3] = procedure.code as uint; // r3 + regs[4] = procedure.env as uint; // r4 + regs[5] = fptr as uint; // r5 + regs[13] = sp as uint; // #52 sp, r13 + regs[14] = rust_bootstrap_green_task as uint; // #56 pc, r14 --> lr } #[cfg(target_arch = "mips")] @@ -271,8 +278,8 @@ type Registers = [uint, ..32]; fn new_regs() -> ~Registers { ~([0, .. 32]) } #[cfg(target_arch = "mips")] -fn initialize_call_frame(regs: &mut Registers, fptr: *c_void, arg: *c_void, - sp: *mut uint) { +fn initialize_call_frame(regs: &mut Registers, fptr: InitFn, arg: uint, + procedure: raw::Procedure, sp: *mut uint) { let sp = align_down(sp); // sp of mips o32 is 8-byte aligned let sp = mut_offset(sp, -2); diff --git a/src/libgreen/coroutine.rs b/src/libgreen/coroutine.rs index c001d40a2465d..b20892886c6da 100644 --- a/src/libgreen/coroutine.rs +++ b/src/libgreen/coroutine.rs @@ -11,8 +11,6 @@ // Coroutines represent nothing more than a context and a stack // segment. -use std::rt::env; - use context::Context; use stack::{StackPool, Stack}; @@ -31,22 +29,6 @@ pub struct Coroutine { } impl Coroutine { - pub fn new(stack_pool: &mut StackPool, - stack_size: Option, - start: proc()) - -> Coroutine { - let stack_size = match stack_size { - Some(size) => size, - None => env::min_stack() - }; - let mut stack = stack_pool.take_stack(stack_size); - let initial_context = Context::new(start, &mut stack); - Coroutine { - current_stack_segment: stack, - saved_context: initial_context - } - } - pub fn empty() -> Coroutine { Coroutine { current_stack_segment: unsafe { Stack::dummy_stack() }, diff --git a/src/libgreen/sched.rs b/src/libgreen/sched.rs index bf6e0c3430e9c..b224b0cabf365 100644 --- a/src/libgreen/sched.rs +++ b/src/libgreen/sched.rs @@ -756,7 +756,7 @@ impl Scheduler { /// Called by a running task to end execution, after which it will /// be recycled by the scheduler for reuse in a new task. - pub fn terminate_current_task(mut ~self, cur: ~GreenTask) { + pub fn terminate_current_task(mut ~self, cur: ~GreenTask) -> ! { // Similar to deschedule running task and then, but cannot go through // the task-blocking path. The task is already dying. let stask = self.sched_task.take_unwrap(); diff --git a/src/libgreen/task.rs b/src/libgreen/task.rs index 6d61f8e391900..2aca72e35f19d 100644 --- a/src/libgreen/task.rs +++ b/src/libgreen/task.rs @@ -19,13 +19,16 @@ //! values. use std::cast; +use std::rt::env; use std::rt::Runtime; -use std::rt::rtio; use std::rt::local::Local; +use std::rt::rtio; use std::rt::task::{Task, BlockedTask, SendMessage}; use std::task::TaskOpts; use std::unstable::mutex::Mutex; +use std::unstable::raw; +use context::Context; use coroutine::Coroutine; use sched::{Scheduler, SchedHandle, RunOnce}; use stack::StackPool; @@ -75,6 +78,50 @@ pub enum Home { HomeSched(SchedHandle), } +/// Trampoline code for all new green tasks which are running around. This +/// function is passed through to Context::new as the initial rust landing pad +/// for all green tasks. This code is actually called after the initial context +/// switch onto a green thread. +/// +/// The first argument to this function is the `~GreenTask` pointer, and the +/// next two arguments are the user-provided procedure for running code. +/// +/// The goal for having this weird-looking function is to reduce the number of +/// allocations done on a green-task startup as much as possible. +extern fn bootstrap_green_task(task: uint, code: *(), env: *()) -> ! { + // Acquire ownership of the `proc()` + let start: proc() = unsafe { + cast::transmute(raw::Procedure { code: code, env: env }) + }; + + // Acquire ownership of the `~GreenTask` + let mut task: ~GreenTask = unsafe { cast::transmute(task) }; + + // First code after swap to this new context. Run our cleanup job + task.pool_id = { + let sched = task.sched.get_mut_ref(); + sched.run_cleanup_job(); + sched.task_state.increment(); + sched.pool_id + }; + + // Convert our green task to a libstd task and then execute the code + // requested. This is the "try/catch" block for this green task and + // is the wrapper for *all* code run in the task. + let mut start = Some(start); + let task = task.swap().run(|| start.take_unwrap()()); + + // Once the function has exited, it's time to run the termination + // routine. This means we need to context switch one more time but + // clean ourselves up on the other end. Since we have no way of + // preserving a handle to the GreenTask down to this point, this + // unfortunately must call `GreenTask::convert`. In order to avoid + // this we could add a `terminate` function to the `Runtime` trait + // in libstd, but that seems less appropriate since the coversion + // method exists. + GreenTask::convert(task).terminate() +} + impl GreenTask { /// Creates a new green task which is not homed to any particular scheduler /// and will not have any contained Task structure. @@ -89,9 +136,20 @@ impl GreenTask { stack_size: Option, home: Home, start: proc()) -> ~GreenTask { + // Allocate ourselves a GreenTask structure let mut ops = GreenTask::new_typed(None, TypeGreen(Some(home))); - let start = GreenTask::build_start_wrapper(start, ops.as_uint()); - ops.coroutine = Some(Coroutine::new(stack_pool, stack_size, start)); + + // Allocate a stack for us to run on + let stack_size = stack_size.unwrap_or_else(|| env::min_stack()); + let mut stack = stack_pool.take_stack(stack_size); + let context = Context::new(bootstrap_green_task, ops.as_uint(), start, + &mut stack); + + // Package everything up in a coroutine and return + ops.coroutine = Some(Coroutine { + current_stack_segment: stack, + saved_context: context, + }); return ops; } @@ -156,46 +214,6 @@ impl GreenTask { } } - /// Builds a function which is the actual starting execution point for a - /// rust task. This function is the glue necessary to execute the libstd - /// task and then clean up the green thread after it exits. - /// - /// The second argument to this function is actually a transmuted copy of - /// the `GreenTask` pointer. Context switches in the scheduler silently - /// transfer ownership of the `GreenTask` to the other end of the context - /// switch, so because this is the first code that is running in this task, - /// it must first re-acquire ownership of the green task. - pub fn build_start_wrapper(start: proc(), ops: uint) -> proc() { - proc() { - // First code after swap to this new context. Run our - // cleanup job after we have re-acquired ownership of the green - // task. - let mut task: ~GreenTask = unsafe { GreenTask::from_uint(ops) }; - task.pool_id = { - let sched = task.sched.get_mut_ref(); - sched.run_cleanup_job(); - sched.task_state.increment(); - sched.pool_id - }; - - // Convert our green task to a libstd task and then execute the code - // requested. This is the "try/catch" block for this green task and - // is the wrapper for *all* code run in the task. - let mut start = Some(start); - let task = task.swap().run(|| start.take_unwrap()()); - - // Once the function has exited, it's time to run the termination - // routine. This means we need to context switch one more time but - // clean ourselves up on the other end. Since we have no way of - // preserving a handle to the GreenTask down to this point, this - // unfortunately must call `GreenTask::convert`. In order to avoid - // this we could add a `terminate` function to the `Runtime` trait - // in libstd, but that seems less appropriate since the coversion - // method exists. - GreenTask::convert(task).terminate(); - } - } - pub fn give_home(&mut self, new_home: Home) { match self.task_type { TypeGreen(ref mut home) => { *home = Some(new_home); } @@ -278,9 +296,9 @@ impl GreenTask { Local::put(self.swap()); } - fn terminate(mut ~self) { + fn terminate(mut ~self) -> ! { let sched = self.sched.take_unwrap(); - sched.terminate_current_task(self); + sched.terminate_current_task(self) } // This function is used to remotely wakeup this green task back on to its diff --git a/src/libstd/unstable/raw.rs b/src/libstd/unstable/raw.rs index 87547997798f5..c25422d24e911 100644 --- a/src/libstd/unstable/raw.rs +++ b/src/libstd/unstable/raw.rs @@ -41,6 +41,12 @@ pub struct Closure { env: *(), } +/// The representation of a Rust procedure (`proc()`) +pub struct Procedure { + code: *(), + env: *(), +} + /// This trait is meant to map equivalences between raw structs and their /// corresponding rust values. pub trait Repr { diff --git a/src/rt/arch/arm/_context.S b/src/rt/arch/arm/_context.S index 4ab463d968ec6..fb6db57414a56 100644 --- a/src/rt/arch/arm/_context.S +++ b/src/rt/arch/arm/_context.S @@ -51,3 +51,11 @@ rust_swap_registers: msr cpsr_cxsf, r2 mov pc, lr + +// For reasons of this existence, see the comments in x86_64/_context.S +.globl rust_bootstrap_green_task +rust_bootstrap_green_task: + mov r0, r0 + mov r1, r3 + mov r2, r4 + mov pc, r5 diff --git a/src/rt/arch/x86_64/_context.S b/src/rt/arch/x86_64/_context.S index 74f20650f304b..36caf7720c40c 100644 --- a/src/rt/arch/x86_64/_context.S +++ b/src/rt/arch/x86_64/_context.S @@ -157,3 +157,36 @@ SWAP_REGISTERS: // Jump to the instruction pointer // found in regs: jmp *(RUSTRT_IP*8)(ARG1) + +// This function below, rust_bootstrap_green_task, is used to initialize a green +// task. This code is the very first code that is run whenever a green task +// starts. The only assumptions that this code makes is that it has a register +// context previously set up by Context::new() and some values are in some +// special registers. +// +// In theory the register context could be set up and then the context switching +// would plop us directly into some 'extern "C" fn', but not all platforms have +// the argument registers saved throughout a context switch (linux doesn't save +// rdi/rsi, the first two argument registers). Instead of modifying all context +// switches, instead the initial data for starting a green thread is shoved into +// unrelated registers (r12/13, etc) which always need to be saved on context +// switches anyway. +// +// With this strategy we get the benefit of being able to pass a fair bit of +// contextual data from the start of a green task to its init function, as well +// as not hindering any context switches. +// +// If you alter this code in any way, you likely need to update +// src/libgreen/context.rs as well. + +#if defined(__APPLE__) +#define BOOTSTRAP _rust_bootstrap_green_task +#else +#define BOOTSTRAP rust_bootstrap_green_task +#endif +.globl BOOTSTRAP +BOOTSTRAP: + mov %r12, RUSTRT_ARG0_S + mov %r13, RUSTRT_ARG1_S + mov %r14, RUSTRT_ARG2_S + jmpq *%r15