initial commit

master
edef 2014-12-23 04:24:40 +01:00
commit e9d6146b5b
10 changed files with 506 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/Cargo.lock

8
Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "lwkt"
version = "0.0.1"
authors = ["edef <edef@edef.eu>"]
build = "build.rs"
[dependencies]
fn_box = "^1.0.1"

48
benches/swap.rs Normal file
View File

@ -0,0 +1,48 @@
#![feature(unboxed_closures, default_type_params, asm)]
extern crate test;
extern crate libc;
extern crate lwkt;
extern crate fn_box;
use test::Bencher;
use lwkt::Context;
use fn_box::FnBox;
use std::ptr::null_mut;
use std::mem::{transmute, forget};
#[bench]
fn swap(b: &mut Bencher) {
let mut native = unsafe { Context::native() };
let f: Box<FnBox() + Send + 'static> = unsafe { transmute((1u, 1u)) };
let mut ctx = box { (&mut native as *mut Context, null_mut()) };
let mut green = Context::new(init, &mut *ctx as *mut _, f);
ctx.1 = &mut green as *mut Context;
fn init(ctx: *mut (*mut Context, *mut Context), f: Box<FnBox()>) -> ! {
unsafe {
let (native, green) = *ctx;
forget(f);
loop { Context::swap(&mut *green, &mut *native); }
}
}
unsafe {
Context::swap(&mut native, &mut green);
}
b.iter(|| unsafe {
Context::swap(&mut native, &mut green);
})
}
#[bench]
fn kernel_swap(b: &mut Bencher) {
b.iter(|| unsafe {
asm!("movq $$102, %rax\n\
syscall"
:
:
: "rax", "rcx"
: "volatile");
});
}

53
build.rs Normal file
View File

@ -0,0 +1,53 @@
use std::io::Command;
use std::io::fs::PathExtensions;
use std::io::fs;
use std::os;
fn main() {
let out_dir = os::getenv("OUT_DIR").unwrap();
let mut objects = Vec::new();
let files = fs::readdir(&Path::new("src")).unwrap();
let mut files = files.iter().filter(|p| p.is_file());
for file in files {
if let Some(filename) = file.filename_str() {
let filepath = format!("src/{}", filename);
let outpath;
if let Some(basename) = eat_extension(filename, ".c") {
outpath = format!("{}/{}.o", out_dir, basename);
Command::new("cc").args(&[filepath.as_slice(), "-c", "-o"])
.arg(outpath.clone())
.status().unwrap();
}
else if let Some(basename) = eat_extension(filename, ".s") {
outpath = format!("{}/{}.o", out_dir, basename);
Command::new("nasm").args(&[filepath.as_slice(), "-felf64", "-o"])
.arg(outpath.clone())
.status().unwrap();
}
else { continue }
objects.push(outpath);
}
}
Command::new("ar").args(&["crus", "libcontext.a"])
.args(objects.as_slice())
.cwd(&Path::new(&out_dir))
.status().unwrap();
println!("cargo:rustc-flags=-L {} -l context:static", out_dir);
}
fn eat_extension<'a>(s: &'a str, ext: &str) -> Option<&'a str> {
if s.ends_with(ext) {
Some(s.slice_to(s.len() - ext.len()))
}
else {
None
}
}

120
src/context.rs Normal file
View File

@ -0,0 +1,120 @@
use libc;
use stack::Stack;
use std::simd::u64x2;
use std::mem::{zeroed, size_of, transmute};
use std::raw;
use fn_box::FnBox;
pub struct Context {
regs: Registers,
stack: Stack
}
#[repr(C)]
#[allow(dead_code)]
struct Registers {
rbx: u64,
rsp: u64,
rbp: u64,
rdi: u64,
r12: u64,
r13: u64,
r14: u64,
r15: u64,
ip: u64,
xmm0: u64x2,
xmm1: u64x2,
xmm2: u64x2,
xmm3: u64x2,
xmm4: u64x2,
xmm5: u64x2,
}
impl Registers {
fn zeroed() -> Registers { unsafe { zeroed() } }
}
extern "C" {
fn lwut_bootstrap();
fn lwut_swapcontext(save: *mut Registers, restore: *mut Registers);
fn lwut_get_sp_limit() -> *const u8;
fn lwut_set_sp_limit(limit: *const u8);
fn lwut_abort() -> !;
}
pub type BoxedFn<Args, Result> = Box<FnBox<Args, Result> + Send + 'static>;
pub type StartFn<T, Args, Result> = fn(data: *mut T, f: BoxedFn<Args, Result>) -> !;
impl Context {
pub fn new<T, Args, Result>(init: StartFn<T, Args, Result>, data: *mut T,
f: BoxedFn<Args, Result>) -> Context {
let stack = Stack::new(4 << 20);
let sp = stack.top() as *mut uint;
let sp = align_down_mut(sp, 16);
let sp = offset_mut(sp, -1);
unsafe {
*sp = 0;
}
let f: raw::TraitObject = unsafe { transmute(f) };
Context {
regs: Registers {
rbp: 0,
rsp: sp as libc::uintptr_t,
ip: lwut_bootstrap as libc::uintptr_t,
r12: lwut_init::<T, Args, Result> as libc::uintptr_t,
rdi: init as libc::uintptr_t,
r13: data as libc::uintptr_t,
r14: f.data as libc::uintptr_t,
r15: f.vtable as libc::uintptr_t,
// r8: …,
// r9: …,
.. Registers::zeroed()
},
stack: stack
}
}
}
unsafe extern "C" fn lwut_init<T, A, R>(start: StartFn<T, A, R>, data: *mut T,
f_data: *mut (), f_vtable: *mut ()) -> ! {
let f: BoxedFn<A, R> = transmute(raw::TraitObject {
data: f_data,
vtable: f_vtable
});
start(data, f)
}
impl Context {
pub unsafe fn native() -> Context {
Context {
regs: Registers {
ip: lwut_abort as libc::uintptr_t,
.. Registers::zeroed()
},
stack: Stack::native(lwut_get_sp_limit())
}
}
#[inline(always)]
pub unsafe fn swap(out_context: &mut Context, in_context: &mut Context) {
lwut_set_sp_limit(in_context.stack.limit());
lwut_swapcontext(&mut out_context.regs, &mut in_context.regs);
}
}
#[inline]
fn align_down_mut<T>(sp: *mut T, n: uint) -> *mut T {
let sp = (sp as uint) & !(n - 1);
sp as *mut T
}
// ptr::offset_mmut is positive ints only
#[inline]
pub fn offset_mut<T>(ptr: *mut T, count: int) -> *mut T {
(ptr as int + count * (size_of::<T>() as int)) as *mut T
}

18
src/debug.c Normal file
View File

@ -0,0 +1,18 @@
#include "valgrind/valgrind.h"
// In order for Valgrind to keep track of stack overflows and such, it needs
// a little help. That help unfortunately comes in the form of a pair of C
// macros. Calling out to un-inlineable C code for this is pointlessly slow,
// but that's the way it is for now.
// Register a stack with Valgrind. start < end. Returns an integer ID that can
// be used to deregister the stack when it's deallocated.
unsigned int lwut_stack_register(const void *start, const void *end) {
return VALGRIND_STACK_REGISTER(start, end);
}
// Deregister a stack from Valgrind. Takes the integer ID that was returned
// on registration.
void lwut_stack_deregister(unsigned int id) {
VALGRIND_STACK_DEREGISTER(id);
}

8
src/lib.rs Normal file
View File

@ -0,0 +1,8 @@
#![feature(default_type_params)]
extern crate libc;
extern crate fn_box;
pub use context::Context;
mod context;
mod stack;

37
src/main.rs Normal file
View File

@ -0,0 +1,37 @@
#![feature(unboxed_closures, default_type_params)]
extern crate lwkt;
extern crate fn_box;
use std::ptr::null_mut;
use std::intrinsics::abort;
use lwkt::Context;
use fn_box::FnBox;
fn main() {
let f = box move |:| {
println!("Hello, world!")
};
let mut native = unsafe { Context::native() };
fn init(ctx: *mut (*mut Context, *mut Context), f: Box<FnBox()>) -> ! {
unsafe {
let (native, green) = *ctx;
f();
Context::swap(&mut *green, &mut *native);
abort();
}
}
let mut ctx = box { (&mut native as *mut Context, null_mut()) };
let mut green = Context::new(init, &mut *ctx as *mut _, f);
ctx.1 = &mut green as *mut Context;
unsafe {
Context::swap(&mut native, &mut green);
}
println!("size_of::<Context>() == {}", std::mem::size_of::<Context>());
}

114
src/platform.s Normal file
View File

@ -0,0 +1,114 @@
; vim: ft=nasm
BITS 64
;; the structure containing every register that is saved on context switches.
;; this needs to match the struct in context.rs, or shit will break badly.
struc context
ctx_rbx resq 1
ctx_rsp resq 1
ctx_rbp resq 1
ctx_rdi resq 1
ctx_r12 resq 1
ctx_r13 resq 1
ctx_r14 resq 1
ctx_r15 resq 1
ctx_ip:
resq 1
alignb 16
ctx_xmm0 resq 2
ctx_xmm1 resq 2
ctx_xmm2 resq 2
ctx_xmm3 resq 2
ctx_xmm4 resq 2
ctx_xmm5 resq 2
endstruc
global lwut_swapcontext
lwut_swapcontext:
;; this is where the actual context switching takes place. first, save every
;; register in the current context into the leaving context, pointed at by rdi,
;; making sure the return address ends up in the IP slot. then, restore every
;; register from the entering context, pointed at by rsi, and jump to the
;; instruction pointer.
pop rax
; save instruction pointer
mov [rdi+ctx_ip], rax
; save non-volatile integer registers (including rsp)
mov [rdi+ctx_rbx], rbx
mov [rdi+ctx_rsp], rsp
mov [rdi+ctx_rbp], rbp
mov [rdi+ctx_r12], r12
mov [rdi+ctx_r13], r13
mov [rdi+ctx_r14], r14
mov [rdi+ctx_r15], r15
; save 0th argument register
mov [rdi+ctx_rdi], rdi
; save non-volatile XMM registers
movapd [rdi+ctx_xmm0], xmm0
movapd [rdi+ctx_xmm1], xmm1
movapd [rdi+ctx_xmm2], xmm2
movapd [rdi+ctx_xmm3], xmm3
movapd [rdi+ctx_xmm4], xmm4
movapd [rdi+ctx_xmm5], xmm5
; restore non-volatile integer registers
mov rbx, [rsi+ctx_rbx]
mov rsp, [rsi+ctx_rsp]
mov rbp, [rsi+ctx_rbp]
mov r12, [rsi+ctx_r12]
mov r13, [rsi+ctx_r13]
mov r14, [rsi+ctx_r14]
mov r15, [rsi+ctx_r15]
; restore 0th argument register
mov rdi, [rsi+ctx_rdi]
; restore non-volatile XMM registers
movapd xmm0, [rsi+ctx_xmm0]
movapd xmm1, [rsi+ctx_xmm1]
movapd xmm2, [rsi+ctx_xmm2]
movapd xmm3, [rsi+ctx_xmm3]
movapd xmm4, [rsi+ctx_xmm4]
movapd xmm5, [rsi+ctx_xmm5]
jmp [rsi+ctx_ip]
global lwut_bootstrap
lwut_bootstrap:
;; some of the parameter registers aren't saved on context switch, and thus
;; can't be set into the struct directly. thus, initialisation from Rust-land
;; places the parameters in unrelated registers, and we frob them into place
;; out here, in assembly-land. below are the parameter registers in order,
;; along with the alternative register used in parentheses, if there is one.
;; rdi, rsi (r13), rdx (r14), rcx (r15), r8, r9
mov rsi, r13
mov rdx, r14
mov rcx, r15
jmp r12
;; Rust stores a stack limit at [fs:0x70]. These two functions set and retrieve
;; the limit. They could alternatively be implemented as #[inline(always)] Rust
;; functions, with inline assembly, but I prefer keeping all the assembly-land
;; stuff in here.
global lwut_set_sp_limit
lwut_set_sp_limit:
mov [fs:0x70], rdi
ret
global lwut_get_sp_limit
lwut_get_sp_limit:
mov rax, [fs:0x70]
ret
global lwut_abort
lwut_abort:
;; when a context is created for a native thread, it should only be switched
;; out of. if it's accidentally switched into, it'll hit this, because that's
;; what we set the initial IP to.
ud2

98
src/stack.rs Normal file
View File

@ -0,0 +1,98 @@
use libc;
use std::ptr;
use std::os::{errno, page_size, MemoryMap, MapReadable, MapWritable,
MapNonStandardFlags};
const STACK_FLAGS: libc::c_int = libc::MAP_STACK
| libc::MAP_PRIVATE
| libc::MAP_ANON;
pub enum Stack {
Native {
sp_limit: *const u8
},
Managed {
buf: MemoryMap,
valgrind_id: libc::c_uint
}
}
extern "C" {
fn lwut_stack_register(start: *const u8, end: *const u8) -> libc::c_uint;
fn lwut_stack_deregister(id: libc::c_uint);
}
impl Stack {
pub fn new(size: uint) -> Stack {
let buf = match MemoryMap::new(size, &[MapReadable, MapWritable,
MapNonStandardFlags(STACK_FLAGS)]) {
Ok(map) => map,
Err(e) => panic!("mmap for stack of size {} failed: {}", size, e)
};
if !protect_last_page(&buf) {
panic!("Could not memory-protect guard page. stack={}, errno={}",
buf.data(), errno());
}
let valgrind_id = unsafe {
lwut_stack_register(buf.data().offset(buf.len() as int) as *const _,
buf.data() as *const _)
};
let stk = Stack::Managed {
buf: buf,
valgrind_id: valgrind_id
};
stk
}
pub unsafe fn native(limit: *const u8) -> Stack {
Stack::Native {
sp_limit: limit
}
}
pub fn top(&self) -> *const u8 {
unsafe {
match *self {
Stack::Native { .. } => ptr::null(),
Stack::Managed { ref buf, .. } => {
buf.data().offset(buf.len() as int) as *const _
}
}
}
}
pub fn limit(&self) -> *const u8 {
unsafe {
match *self {
Stack::Native { sp_limit, .. } => sp_limit,
Stack::Managed { ref buf, .. } => {
buf.data().offset(page_size() as int) as *const _
}
}
}
}
}
fn protect_last_page(stack: &MemoryMap) -> bool {
unsafe {
let last_page = stack.data() as *mut libc::c_void;
libc::mprotect(last_page, page_size() as libc::size_t,
libc::PROT_NONE) != -1
}
}
impl Drop for Stack {
fn drop(&mut self) {
match *self {
Stack::Native { .. } => {},
Stack::Managed { valgrind_id, .. } => unsafe {
lwut_stack_deregister(valgrind_id);
}
}
}
}