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