initial commit

This commit is contained in:
minhtrannhat 2024-09-19 19:05:26 -04:00
commit b558986218
Signed by: minhtrannhat
GPG Key ID: E13CFA85C53F8062
8 changed files with 204 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View 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
View File

@ -0,0 +1,6 @@
[package]
name = "rusty-fiber"
version = "0.1.0"
edition = "2021"
[dependencies]

3
README.md Normal file
View 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
View File

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

20
src/main.rs Normal file
View 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
View 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
View 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,
}
}
}