From b558986218e9128ea18379dcd9c982c8b4ae1460 Mon Sep 17 00:00:00 2001 From: minhtrannhat Date: Thu, 19 Sep 2024 19:05:26 -0400 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 7 +++ Cargo.toml | 6 +++ README.md | 3 ++ rust-toolchain.toml | 2 + src/main.rs | 20 ++++++++ src/runtime.rs | 110 ++++++++++++++++++++++++++++++++++++++++++++ src/thread.rs | 55 ++++++++++++++++++++++ 8 files changed, 204 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 rust-toolchain.toml create mode 100644 src/main.rs create mode 100644 src/runtime.rs create mode 100644 src/thread.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ac2d35d --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3612c20 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rusty-fiber" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..1edaead --- /dev/null +++ b/README.md @@ -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. diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..eeaafbc --- /dev/null +++ b/src/main.rs @@ -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!"); +} diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 0000000..0280208 --- /dev/null +++ b/src/runtime.rs @@ -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, + 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 = (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() + } +} diff --git a/src/thread.rs b/src/thread.rs new file mode 100644 index 0000000..b4d0023 --- /dev/null +++ b/src/thread.rs @@ -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, + 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, + } + } +}