From e9d6146b5b6e044608ed7b2c718b398d406ab242 Mon Sep 17 00:00:00 2001 From: edef Date: Tue, 23 Dec 2014 04:24:40 +0100 Subject: [PATCH] initial commit --- .gitignore | 2 + Cargo.toml | 8 ++++ benches/swap.rs | 48 +++++++++++++++++++ build.rs | 53 +++++++++++++++++++++ src/context.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++++ src/debug.c | 18 ++++++++ src/lib.rs | 8 ++++ src/main.rs | 37 +++++++++++++++ src/platform.s | 114 +++++++++++++++++++++++++++++++++++++++++++++ src/stack.rs | 98 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 506 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 benches/swap.rs create mode 100644 build.rs create mode 100644 src/context.rs create mode 100644 src/debug.c create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/platform.s create mode 100644 src/stack.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3c8617b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "lwkt" +version = "0.0.1" +authors = ["edef "] +build = "build.rs" + +[dependencies] +fn_box = "^1.0.1" diff --git a/benches/swap.rs b/benches/swap.rs new file mode 100644 index 0000000..a2d9a36 --- /dev/null +++ b/benches/swap.rs @@ -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 = 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) -> ! { + 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"); + }); +} diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..2c495a8 --- /dev/null +++ b/build.rs @@ -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 + } +} diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..66b84e1 --- /dev/null +++ b/src/context.rs @@ -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 = Box + Send + 'static>; +pub type StartFn = fn(data: *mut T, f: BoxedFn) -> !; + +impl Context { + pub fn new(init: StartFn, data: *mut T, + f: BoxedFn) -> 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:: 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(start: StartFn, data: *mut T, + f_data: *mut (), f_vtable: *mut ()) -> ! { + let f: BoxedFn = 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(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(ptr: *mut T, count: int) -> *mut T { + (ptr as int + count * (size_of::() as int)) as *mut T +} diff --git a/src/debug.c b/src/debug.c new file mode 100644 index 0000000..69ab11e --- /dev/null +++ b/src/debug.c @@ -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); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2f079a7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +#![feature(default_type_params)] +extern crate libc; +extern crate fn_box; + +pub use context::Context; + +mod context; +mod stack; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0a6d1a6 --- /dev/null +++ b/src/main.rs @@ -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) -> ! { + 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::() == {}", std::mem::size_of::()); +} diff --git a/src/platform.s b/src/platform.s new file mode 100644 index 0000000..8d228d0 --- /dev/null +++ b/src/platform.s @@ -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 diff --git a/src/stack.rs b/src/stack.rs new file mode 100644 index 0000000..b0c057f --- /dev/null +++ b/src/stack.rs @@ -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); + } + } + } +}