initial commit
This commit is contained in:
commit
b558986218
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "rusty-fiber"
|
||||
version = "0.1.0"
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "rusty-fiber"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Rusty Fibers
|
||||
|
||||
Fiber is a lightweight thread of execution that uses _cooperative multitasking_. This is the basis we can build our future async runtime.
|
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
20
src/main.rs
Normal file
20
src/main.rs
Normal file
@ -0,0 +1,20 @@
|
||||
/* Rust Fibers Runtime aka Green Stackful Coroutines Threads */
|
||||
|
||||
/*
|
||||
* Also known as Green Threads in Java
|
||||
* and Green process in Erlang */
|
||||
|
||||
/* Rust functions normally have prologue and epilogue
|
||||
* proceedures to setup/tear down certain CPU registers
|
||||
* (setting up the stack frames)
|
||||
*
|
||||
* `naked_function` simply means that we want to do
|
||||
* the prologue and epilogue proceedures ourselves*/
|
||||
#![feature(naked_functions)]
|
||||
|
||||
mod runtime;
|
||||
mod thread;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
110
src/runtime.rs
Normal file
110
src/runtime.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use std::arch::asm;
|
||||
|
||||
use crate::thread::Thread;
|
||||
use crate::thread::ThreadContext;
|
||||
use crate::thread::ThreadState;
|
||||
|
||||
pub const DEFAULT_STACK_SIZE: usize = 1024 * 1024 * 2; // 2MB
|
||||
const MAX_THREADS: usize = 4;
|
||||
|
||||
// global mutable pointer to our runtime
|
||||
pub static mut RUNTIME: usize = 0;
|
||||
|
||||
pub struct Runtime {
|
||||
threads: Vec<Thread>,
|
||||
current_thread: usize, // pointer to which thread we're running
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the Runtime to the base state
|
||||
*/
|
||||
impl Runtime {
|
||||
pub fn new() -> Self {
|
||||
let base_thread = Thread {
|
||||
stack: vec![0_u8; DEFAULT_STACK_SIZE],
|
||||
ctx: ThreadContext::default(),
|
||||
state: ThreadState::Running,
|
||||
};
|
||||
|
||||
let mut threads = vec![base_thread];
|
||||
|
||||
let mut available_threads: Vec<Thread> = (1..MAX_THREADS).map(|_| Thread::new()).collect();
|
||||
|
||||
threads.append(&mut available_threads);
|
||||
|
||||
Runtime {
|
||||
threads,
|
||||
current_thread: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We want to initialize the global mutable runtime ptr
|
||||
*/
|
||||
pub fn init(&self) {
|
||||
unsafe {
|
||||
let runtime_ptr: *const Runtime = self;
|
||||
RUNTIME = runtime_ptr as usize;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> ! {
|
||||
// runs till t_yield() if false then we exit
|
||||
while self.t_yield() {}
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
// return function for when a thread is finished
|
||||
fn t_return(&mut self) {
|
||||
if self.current_thread != 0 {
|
||||
self.threads[self.current_thread].state = ThreadState::Available;
|
||||
self.t_yield(); // yield control to other tasks
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* t_yield is our runtime's scheduler
|
||||
*
|
||||
* It will go through all the threads and
|
||||
* see if any of them are in the /READY/ state,
|
||||
* (i.e a database call is finished)
|
||||
*
|
||||
* If no threads are /READY/, we're finished
|
||||
*
|
||||
* This is only a simple Round-Robin scheduler
|
||||
*/
|
||||
#[inline(never)] // rustc plz dont optimize
|
||||
fn t_yield(&mut self) -> bool {
|
||||
let mut pos = self.current_thread;
|
||||
|
||||
// going through all the threads that we have
|
||||
while self.threads[pos].state != ThreadState::Ready {
|
||||
pos += 1;
|
||||
if pos == self.threads.len() {
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if pos == self.current_thread {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// we found at least one thread in READY state
|
||||
if self.threads[self.current_thread].state != ThreadState::Available {
|
||||
self.threads[self.current_thread].state = ThreadState::Ready;
|
||||
};
|
||||
|
||||
self.threads[pos].state = ThreadState::Running;
|
||||
let old_pos = self.current_thread;
|
||||
self.current_thread = pos;
|
||||
|
||||
unsafe {
|
||||
let old: *mut ThreadContext = &mut self.threads[old_pos].ctx;
|
||||
let new: *const ThreadContext = &self.threads[pos].ctx;
|
||||
|
||||
asm!("call switch", in("rdi") old, in("rsi") new, clobber_abi("C"))
|
||||
}
|
||||
|
||||
!self.threads.is_empty()
|
||||
}
|
||||
}
|
55
src/thread.rs
Normal file
55
src/thread.rs
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Each Thread got its own stack, threadcontext (for the registers that CPU needs to resume) and
|
||||
* state of the thread
|
||||
*/
|
||||
|
||||
use crate::runtime::DEFAULT_STACK_SIZE;
|
||||
|
||||
pub struct Thread {
|
||||
pub stack: Vec<u8>,
|
||||
pub ctx: ThreadContext,
|
||||
pub state: ThreadState,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum ThreadState {
|
||||
Available, // Thread ready to be assigned to a task
|
||||
Running, // Thread running
|
||||
Ready, // Thread ready to move forward and resume execution (i.e after a blocking network syscall)
|
||||
}
|
||||
/* ThreadContext struct
|
||||
*
|
||||
* - Assuming the machine's arch is x86_64
|
||||
*/
|
||||
|
||||
/*
|
||||
* Preserving callee-saved registers:
|
||||
* The callee must preserve the values of specific registers
|
||||
* (like rbx, rbp, r12-r15) if it modifies them.
|
||||
* It must restore these registers
|
||||
* to their original values before returning.
|
||||
*/
|
||||
#[derive(Debug, Default)]
|
||||
#[repr(C)]
|
||||
pub struct ThreadContext {
|
||||
rsp: u64,
|
||||
r15: u64,
|
||||
r14: u64,
|
||||
r13: u64,
|
||||
r12: u64,
|
||||
rbx: u64,
|
||||
rbp: u64,
|
||||
}
|
||||
|
||||
/*
|
||||
* Once a stack is allocated, it must not move otherwise all pointers are invalidated
|
||||
*/
|
||||
impl Thread {
|
||||
pub fn new() -> Self {
|
||||
Thread {
|
||||
stack: vec![0_u8; DEFAULT_STACK_SIZE],
|
||||
ctx: ThreadContext::default(),
|
||||
state: ThreadState::Available,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user