forked from MightyPork/crsn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
12 KiB
343 lines
12 KiB
use std::any::{Any, TypeId};
|
|
use std::collections::{HashMap, VecDeque};
|
|
|
|
use crate::asm::data::{Rd, RdData, RdObj, Register, Wr, WrData};
|
|
use crate::asm::data::literal::{Addr, Value};
|
|
use crate::asm::instr::Cond;
|
|
use crate::runtime::fault::Fault;
|
|
use crate::runtime::frame::{CallStack, REG_COUNT, StackFrame};
|
|
use std::sync::Arc;
|
|
use crate::runtime::run_thread::ThreadInfo;
|
|
use nudge::{likely};
|
|
use crate::asm::instr::cond::Flag;
|
|
use std::fmt::{Debug, Formatter};
|
|
use std::fmt;
|
|
use std::time::Instant;
|
|
|
|
pub struct RunState {
|
|
pub thread_info: Arc<ThreadInfo>,
|
|
/// The active coroutine
|
|
pub cr: CoroutineContext,
|
|
/// Parked coroutines
|
|
pub parked: VecDeque<CoroutineContext>,
|
|
/// General purpose registers that stay valid for the entire run-time of the thread
|
|
pub global_regs: [Value; REG_COUNT],
|
|
/// Extension data
|
|
pub ext_data: ExtensionDataStore,
|
|
/// Execution deadline, if multi-tasked
|
|
pub cr_deadline: Option<Instant>,
|
|
/// Nonzero if inside a critical section
|
|
pub critical_section: Value,
|
|
}
|
|
|
|
#[derive(Debug,Default)]
|
|
pub struct CoroutineContext {
|
|
pub handle: Value,
|
|
/// Active stack frame
|
|
pub frame: StackFrame,
|
|
/// Call stack
|
|
pub call_stack: CallStack,
|
|
/// Coroutine run state
|
|
pub cr_state: CoroutineState,
|
|
}
|
|
|
|
/// Execution state of an inactive coroutine
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
pub enum CoroutineState {
|
|
/// Ready to run, just started, or interrupted by the scheduler
|
|
Ready,
|
|
/// Delay in progress
|
|
Sleep { due: Instant },
|
|
/// The task finished
|
|
Finished(Vec<Value>),
|
|
/// The task yielded a value that must be consumed before it can resume.
|
|
/// State switches to Ready when the value is read.
|
|
YieldedValue(Value),
|
|
}
|
|
|
|
impl CoroutineState {
|
|
pub fn is_alive(&self) -> bool {
|
|
match self {
|
|
CoroutineState::Ready => true,
|
|
CoroutineState::Sleep { .. } => true,
|
|
CoroutineState::Finished(_) => false,
|
|
CoroutineState::YieldedValue(_) => true,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for CoroutineState {
|
|
fn default() -> Self {
|
|
Self::Ready
|
|
}
|
|
}
|
|
|
|
impl RunState {
|
|
/// Clear everything. Caution: when done at runtime, this effectively reboots the thread
|
|
pub fn reset(&mut self) {
|
|
self.cr = Default::default();
|
|
self.parked = Default::default();
|
|
self.global_regs = Default::default();
|
|
self.ext_data = Default::default();
|
|
}
|
|
|
|
/// Add a coroutine, marked as Ready, to run next
|
|
pub fn add_coroutine(&mut self, frame : StackFrame) -> Value {
|
|
let handle = self.thread_info.unique_handle();
|
|
trace!("Spawn cr {:#}", handle);
|
|
|
|
// front - so it runs ASAP
|
|
self.parked.push_front(CoroutineContext {
|
|
handle,
|
|
frame,
|
|
call_stack: vec![],
|
|
cr_state: Default::default()
|
|
});
|
|
if self.cr_deadline.is_none() {
|
|
// start context switching
|
|
self.cr_deadline = Some(Instant::now() + *self.thread_info.scheduler_interval.read());
|
|
}
|
|
handle
|
|
}
|
|
}
|
|
|
|
impl Debug for RunState {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("RunState")
|
|
.field("cr", &self.cr)
|
|
.field("parked", &self.parked)
|
|
.field("global_regs", &self.global_regs)
|
|
//.field("ext_data", &self.ext_data)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug,Default)]
|
|
pub struct ExtensionDataStore {
|
|
store: HashMap<TypeId, Box<dyn Any + Send>>
|
|
}
|
|
|
|
impl ExtensionDataStore {
|
|
pub fn new() -> Self {
|
|
Default::default()
|
|
}
|
|
|
|
/// Get an extension's persistent data object for mutation.
|
|
/// It will be created using Default if not present before.
|
|
pub fn get_mut<T: Send + Default + 'static>(&mut self) -> &mut T {
|
|
if !self.store.contains_key(&TypeId::of::<T>()) {
|
|
self.store.insert(TypeId::of::<T>(), Box::new(T::default()));
|
|
}
|
|
|
|
self.store.get_mut(&TypeId::of::<T>()).unwrap()
|
|
.downcast_mut().unwrap()
|
|
}
|
|
}
|
|
|
|
impl RunState {
|
|
/// Get an extension's persistent data object for mutation.
|
|
/// It will be created using Default if not present before.
|
|
pub fn ext_mut<T: Send + Default + 'static>(&mut self) -> &mut T {
|
|
self.ext_data.get_mut()
|
|
}
|
|
|
|
/// Set a status flag. Only supports simple, positive conds (i.e. not GreaterOrEqual)
|
|
#[inline(always)]
|
|
pub fn set_flag(&mut self, flag: Flag, set: bool) {
|
|
trace!("Flag {} = {:?}", flag, set);
|
|
self.cr.frame.status.set(flag, set);
|
|
}
|
|
|
|
/// Check status flags for a condition
|
|
#[inline(always)]
|
|
pub fn test_cond(&self, cond: Cond) -> bool {
|
|
self.cr.frame.status.test(cond)
|
|
}
|
|
|
|
/// Set program counter - address of the next instruction to run
|
|
pub fn set_pc(&mut self, pc: Addr) {
|
|
trace!("PC := {}", pc);
|
|
self.cr.frame.pc = pc;
|
|
}
|
|
|
|
/// Get program counter - address of the next instruction to run
|
|
pub fn get_pc(&self) -> Addr {
|
|
self.cr.frame.pc
|
|
}
|
|
|
|
/// Clear status flags
|
|
#[inline(always)]
|
|
pub fn clear_status(&mut self) {
|
|
self.cr.frame.status.clear();
|
|
}
|
|
|
|
/// Update status flags using a variable.
|
|
/// The update is additive - call `clear_status()` first if desired!
|
|
#[inline(always)]
|
|
pub fn update_status(&mut self, val: Value) {
|
|
self.cr.frame.status.update(val);
|
|
}
|
|
|
|
/// Update status flags using a variable.
|
|
/// The update is additive - call `clear_status()` first if desired!
|
|
#[inline(always)]
|
|
pub fn update_status_float(&mut self, val: f64) {
|
|
self.cr.frame.status.update_float(val);
|
|
}
|
|
|
|
/// Read object handle value
|
|
pub fn read_obj(&mut self, rdo: impl Into<RdObj>) -> Result<Value, Fault> {
|
|
rdo.into().read(self)
|
|
}
|
|
|
|
/// Read a `Rd` value
|
|
#[inline]
|
|
pub fn read(&mut self, rd: impl Into<Rd>) -> Result<Value, Fault> {
|
|
let rd = rd.into();
|
|
// TODO dedupe
|
|
match rd.0 {
|
|
RdData::Immediate(v) => Ok(v),
|
|
RdData::Register(Register::Gen(rn)) => {
|
|
if likely(rn < REG_COUNT as u8) {
|
|
trace!("Rd {:?} = {}", rd, self.cr.frame.gen[rn as usize]);
|
|
Ok(self.cr.frame.gen[rn as usize])
|
|
} else {
|
|
Err(Fault::RegisterNotExist { reg: Register::Gen(rn) })
|
|
}
|
|
}
|
|
RdData::Register(Register::Global(rn)) => {
|
|
if likely(rn < REG_COUNT as u8) {
|
|
trace!("Rd {:?} = {}", rd, self.global_regs[rn as usize]);
|
|
Ok(self.global_regs[rn as usize])
|
|
} else {
|
|
Err(Fault::RegisterNotExist { reg: Register::Global(rn) })
|
|
}
|
|
}
|
|
RdData::Register(Register::Arg(rn)) => {
|
|
if likely(rn < REG_COUNT as u8) {
|
|
trace!("Rd {:?} = {}", rd, self.cr.frame.arg[rn as usize]);
|
|
Ok(self.cr.frame.arg[rn as usize])
|
|
} else {
|
|
Err(Fault::RegisterNotExist { reg: Register::Arg(rn) })
|
|
}
|
|
}
|
|
RdData::Register(Register::Res(rn)) => {
|
|
if likely(rn < REG_COUNT as u8) {
|
|
trace!("Rd {:?} = {}", rd, self.cr.frame.res[rn as usize]);
|
|
Ok(self.cr.frame.res[rn as usize])
|
|
} else {
|
|
Err(Fault::RegisterNotExist { reg: Register::Res(rn) }) // TODO use match after @ when stabilized https://github.com/rust-lang/rust/issues/65490
|
|
}
|
|
}
|
|
RdData::RegObject(register) => {
|
|
let reference = self.read(Rd::new(RdData::Register(register)))?;
|
|
self.read_object(reference)
|
|
}
|
|
RdData::ImmObject(reference) => {
|
|
self.read_object(reference)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn read_object(&mut self, reference: Value) -> Result<Value, Fault> {
|
|
// values yielded from coroutines
|
|
for cr in &mut self.parked {
|
|
if cr.handle == reference {
|
|
match cr.cr_state {
|
|
CoroutineState::Ready | CoroutineState::Sleep { .. } => {
|
|
return Err(Fault::Blocked);
|
|
}
|
|
CoroutineState::Finished(_) => {
|
|
self.set_flag(Flag::Eof, true);
|
|
warn!("Attempt to read yielded value of a finished coroutine");
|
|
return Ok(0);
|
|
}
|
|
CoroutineState::YieldedValue(v) => {
|
|
cr.cr_state = CoroutineState::Ready;
|
|
return Ok(v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is a shitty dirty hack to allow iterating over the extensions while passing a mutable reference
|
|
// to self to the reading methods. Since the extensions array is in an Arc, it can't be mutated internally
|
|
// anyway, and we know it will still live after the method returns - unless someone does something incredibly stupid.
|
|
let ti: &ThreadInfo = unsafe { &*(self.thread_info.as_ref() as *const _) }; // If this fails, just clone the arc for the loop...
|
|
|
|
for ext in ti.extensions.iter() {
|
|
if let Some(value) = ext.read_obj(self, reference)? {
|
|
return Ok(value);
|
|
}
|
|
}
|
|
|
|
Err(Fault::ObjectNotExist(reference))
|
|
}
|
|
|
|
fn write_object(&mut self, reference: Value, value: Value) -> Result<(), Fault> {
|
|
// This is a shitty dirty hack to allow iterating over the extensions while passing a mutable reference
|
|
// to self to the reading methods. Since the extensions array is in an Arc, it can't be mutated internally
|
|
// anyway, and we know it will still live after the method returns - unless someone does something incredibly stupid.
|
|
let ti: &ThreadInfo = unsafe { &*(self.thread_info.as_ref() as *const _) }; // If this fails, just clone the arc for the loop...
|
|
|
|
for ext in ti.extensions.iter() {
|
|
if ext.write_obj(self, reference, value)?.is_some() {
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
Err(Fault::ObjectNotExist(reference))
|
|
}
|
|
|
|
/// Write a value to a `Wr` location
|
|
pub fn write(&mut self, wr: impl Into<Wr>, val: Value) -> Result<(), Fault> {
|
|
let wr = wr.into();
|
|
|
|
trace!("WR {:?} := {}", wr, val);
|
|
|
|
// TODO dedupe
|
|
match wr.0 {
|
|
WrData::Register(Register::Gen(rn)) => {
|
|
if likely(rn < REG_COUNT as u8) {
|
|
self.cr.frame.gen[rn as usize] = val;
|
|
Ok(())
|
|
} else {
|
|
Err(Fault::RegisterNotExist { reg: Register::Gen(rn) })
|
|
}
|
|
}
|
|
WrData::Register(Register::Global(rn)) => {
|
|
if likely(rn < REG_COUNT as u8) {
|
|
self.global_regs[rn as usize] = val;
|
|
Ok(())
|
|
} else {
|
|
Err(Fault::RegisterNotExist { reg: Register::Global(rn) })
|
|
}
|
|
}
|
|
WrData::Register(Register::Arg(rn)) => {
|
|
if likely(rn < REG_COUNT as u8) {
|
|
Err(Fault::RegisterNotWritable { reg: Register::Res(rn) })
|
|
} else {
|
|
Err(Fault::RegisterNotExist { reg: Register::Arg(rn) })
|
|
}
|
|
}
|
|
WrData::Register(Register::Res(rn)) => {
|
|
if likely(rn < REG_COUNT as u8) {
|
|
Err(Fault::RegisterNotWritable { reg: Register::Res(rn) })
|
|
} else {
|
|
Err(Fault::RegisterNotExist { reg: Register::Res(rn) }) // TODO use match after @ when stabilized https://github.com/rust-lang/rust/issues/65490
|
|
}
|
|
}
|
|
WrData::Discard => {
|
|
/* Discard */
|
|
Ok(())
|
|
}
|
|
WrData::RegObject(register) => {
|
|
let reference = self.read(Rd::new(RdData::Register(register)))?;
|
|
self.write_object(reference, val)
|
|
}
|
|
WrData::ImmObject(reference) => {
|
|
self.write_object(reference, val)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|