initial commit
This commit is contained in:
commit
e9d6146b5b
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/Cargo.lock
|
|
@ -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"
|
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
#![feature(default_type_params)]
|
||||||
|
extern crate libc;
|
||||||
|
extern crate fn_box;
|
||||||
|
|
||||||
|
pub use context::Context;
|
||||||
|
|
||||||
|
mod context;
|
||||||
|
mod stack;
|
|
@ -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>());
|
||||||
|
}
|
|
@ -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
|
|
@ -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