initial commit
This commit is contained in:
commit
e9d6146b5b
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
8
Cargo.toml
Normal file
8
Cargo.toml
Normal 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
48
benches/swap.rs
Normal 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
53
build.rs
Normal 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
120
src/context.rs
Normal 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
18
src/debug.c
Normal 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
8
src/lib.rs
Normal 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
37
src/main.rs
Normal 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
114
src/platform.s
Normal 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
98
src/stack.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user