Compare commits

..

No commits in common. 'f87d8224327aecaa54b710336f02acd2410aaaa0' and '9cd03ca8e300ecdd263369d374e3bceceefb95a0' have entirely different histories.

  1. 36
      README.md
  2. 31
      crsn/src/asm/instr/flatten.rs
  3. 15
      crsn/src/asm/mod.rs
  4. 8
      crsn/src/asm/parse/mod.rs
  5. 11
      crsn/src/asm/parse/parse_instr.rs
  6. 6
      crsn/src/builtin/defs.rs
  7. 31
      crsn/src/builtin/exec.rs
  8. 80
      crsn/src/builtin/mod.rs
  9. 24
      crsn/src/builtin/parse.rs
  10. 9
      crsn/src/runtime/fault.rs
  11. 172
      crsn/src/runtime/run_thread.rs
  12. 3
      crsn/src/runtime/run_thread/info.rs
  13. 20
      crsn/src/runtime/run_thread/state.rs
  14. 0
      examples/coroutines2.csn
  15. 44
      examples/coroutines3-crit.csn

@ -326,29 +326,6 @@ You can also use one register for up to 64 mutexes.
Remember to only use global registers (or buffer items) as mutexes: `g0`-`g15`. Each coroutine has its own set of *regular* registers. Remember to only use global registers (or buffer items) as mutexes: `g0`-`g15`. Each coroutine has its own set of *regular* registers.
Another way to avoid race conditions is to use **critical sections**.
Context switch (switching between active coroutines) is forbidden in a critical section. Try to keep critical sections as short as possible,
since they can distort sleep times and cause other similar problems.
```
(crit-begin)
...
(crit-end)
```
A safer way is to use the "critical block", which expands to the same, but also detects some common bugs at compile time,
like trying to jump out of the critical section.
```
(crit
...
)
```
Critical section nesting is allowed, but probably a bug.
**Beware deadlocks!**
### Using coroutines as generators ### Using coroutines as generators
A coroutine can "yield a value" by invoking the `yield` instruction with an operand. This can be done any number of times. A coroutine can "yield a value" by invoking the `yield` instruction with an operand. This can be done any number of times.
@ -520,16 +497,6 @@ Jumping to a label is always safer than a manual skip.
; Wait for a coroutine to complete, read its return values and delete it. ; Wait for a coroutine to complete, read its return values and delete it.
(join @Obj) (join @Obj)
; Begin a critical section (no context switch allowed)
(crit-begin)
; End a critical section
(crit-end)
; Shortcut to define a critical section
(crit
...ops
)
; Generate a run-time fault with a debugger message ; Generate a run-time fault with a debugger message
(fault) (fault)
(fault message) (fault message)
@ -545,9 +512,6 @@ Jumping to a label is always safer than a manual skip.
; The label can be a numeric or string label, its sole purpose is tying the two together. They must be unique in the program. ; The label can be a numeric or string label, its sole purpose is tying the two together. They must be unique in the program.
(barrier-open LABEL) (barrier-open LABEL)
(barrier-close LABEL) (barrier-close LABEL)
; Set coroutine scheduler timeslice (in microseconds). Set to zero to disable preemption.
(rt-opt RT_TIMESLICE Rd'usec)
``` ```
## Arithmetic Module ## Arithmetic Module

@ -29,16 +29,6 @@ impl Flatten for () {
} }
} }
impl Flatten for Op {
fn flatten(self: Box<Self>, _label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError> {
Ok(vec![*self])
}
fn pos(&self) -> SourcePosition {
self.pos.clone()
}
}
impl Flatten for InstrWithBranches { impl Flatten for InstrWithBranches {
fn pos(&self) -> SourcePosition { fn pos(&self) -> SourcePosition {
self.pos.clone() self.pos.clone()
@ -121,23 +111,6 @@ impl Flatten for Vec<Box<dyn Flatten>> {
} }
} }
impl Flatten for Vec<Op> {
fn pos(&self) -> SourcePosition {
match self.first() {
None => {
Default::default()
}
Some(f) => {
f.pos()
}
}
}
fn flatten(self: Box<Self>, _label_num: &AtomicU32) -> Result<Vec<Op>, CrsnError> {
Ok(*self)
}
}
impl Flatten for Routine { impl Flatten for Routine {
fn pos(&self) -> SourcePosition { fn pos(&self) -> SourcePosition {
self.pos.clone() self.pos.clone()
@ -165,12 +138,12 @@ impl Flatten for Routine {
}.into_op(self_pos) }.into_op(self_pos)
); );
jumps_to_skips(ops) labels_to_skips(ops)
} }
} }
/// Convert jumps to relative skips /// Convert jumps to relative skips
pub fn jumps_to_skips(ops: Vec<Op>) -> Result<Vec<Op>, CrsnError> { pub fn labels_to_skips(ops: Vec<Op>) -> Result<Vec<Op>, CrsnError> {
let mut label_positions = HashMap::<Label, usize>::new(); let mut label_positions = HashMap::<Label, usize>::new();
for (n, op) in ops.iter().enumerate() { for (n, op) in ops.iter().enumerate() {
if let OpKind::BuiltIn(BuiltinOp::Label(name)) = &op.kind { if let OpKind::BuiltIn(BuiltinOp::Label(name)) = &op.kind {

@ -3,14 +3,13 @@ use std::sync::Arc;
use sexp::SourcePosition; use sexp::SourcePosition;
use crate::asm::instr::flatten::jumps_to_skips; use crate::asm::instr::flatten::labels_to_skips;
use crate::asm::parse::{ParserContext, ParserState}; use crate::asm::parse::{ParserContext, ParserState};
use crate::module::{CrsnExtension, CrsnUniq}; use crate::module::{CrsnExtension, CrsnUniq};
use crate::runtime::program::Program; use crate::runtime::program::Program;
use crate::builtin::BuiltinOps; use crate::builtin::BuiltinOps;
use crate::runtime::run_thread::{RunState, ThreadInfo, ThreadToken}; use crate::runtime::run_thread::{RunState, ThreadInfo, ThreadToken};
use crate::runtime::frame::REG_COUNT; use crate::runtime::frame::REG_COUNT;
use std::sync::atomic::AtomicU32;
pub mod data; pub mod data;
pub mod error; pub mod error;
@ -47,10 +46,6 @@ pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExt
extensions: parsers_arc.clone(), extensions: parsers_arc.clone(),
}); });
/* numbered labels start with a weird high number
to avoid conflicts with user-defined numbered labels */
let label_num = Arc::new(AtomicU32::new(0x7890_0000));
let pcx = ParserContext { let pcx = ParserContext {
parsers: &parsers_arc, parsers: &parsers_arc,
state: RefCell::new(ParserState { state: RefCell::new(ParserState {
@ -67,17 +62,15 @@ pub fn assemble(source: &str, uniq : &CrsnUniq, mut parsers: Vec<Box<dyn CrsnExt
parked: Default::default(), parked: Default::default(),
global_regs: [0; REG_COUNT], global_regs: [0; REG_COUNT],
ext_data: Default::default(), ext_data: Default::default(),
cr_deadline: None, cr_deadline: None
critical_section: 0,
}, },
const_eval_ti: ti, const_eval_ti: ti,
parsing_expr: false, parsing_expr: false
label_num: label_num.clone()
}), }),
}; };
let ops = parse::parse(source, &SourcePosition::default(), &pcx)?; let ops = parse::parse(source, &SourcePosition::default(), &pcx)?;
let ops = jumps_to_skips(ops)?; let ops = labels_to_skips(ops)?;
Ok(Program::new(ops, parsers_arc)?) Ok(Program::new(ops, parsers_arc)?)
} }

@ -51,9 +51,6 @@ pub struct ParserState {
/// True if we are in an expression parser context /// True if we are in an expression parser context
pub parsing_expr : bool, pub parsing_expr : bool,
/// Label numberer
pub label_num : Arc<AtomicU32>,
} }
impl ParserState { impl ParserState {
@ -70,6 +67,9 @@ impl ParserState {
pub fn parse(source: &str, pos: &SourcePosition, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> { pub fn parse(source: &str, pos: &SourcePosition, parsers: &ParserContext) -> Result<Vec<Op>, CrsnError> {
let (items, _pos) = expect_list(sexp::parse(source)?, true)?; let (items, _pos) = expect_list(sexp::parse(source)?, true)?;
/* numbered labels start with a weird high number
to avoid conflicts with user-defined numbered labels */
let label_num = AtomicU32::new(0x7890_0000);
parse_instructions(items.into_iter(), pos, parsers)? parse_instructions(items.into_iter(), pos, parsers)?
.flatten(&parsers.state.borrow().label_num) .flatten(&label_num)
} }

@ -14,7 +14,7 @@ use super::parse_op::parse_op;
pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition, pcx: &ParserContext) -> Result<Box<dyn Flatten>, CrsnError> { pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition, pcx: &ParserContext) -> Result<Box<dyn Flatten>, CrsnError> {
let mut parsed = vec![]; let mut parsed = vec![];
'exprs: for expr in items { for expr in items {
let (tokens, listpos) = expect_list(expr, false)?; let (tokens, listpos) = expect_list(expr, false)?;
let mut toki = tokens.into_iter(); let mut toki = tokens.into_iter();
@ -40,13 +40,8 @@ pub fn parse_instructions(items: impl Iterator<Item=Sexp>, pos: &SourcePosition,
let mut token_parser = TokenParser::new(toki.collect(), &listpos, pcx); let mut token_parser = TokenParser::new(toki.collect(), &listpos, pcx);
for p in pcx.parsers { for p in pcx.parsers {
token_parser = match p.parse_syntax(pos, &name, token_parser) { token_parser = match p.parse_syntax(pos, &name, token_parser) {
Ok(ParseRes::Parsed(op)) => { Ok(ParseRes::Parsed(op)) => return Ok(op),
parsed.push(op); Ok(ParseRes::ParsedNone) => return Ok(Box::new(())),
continue 'exprs;
},
Ok(ParseRes::ParsedNone) => {
continue 'exprs;
},
Ok(ParseRes::Unknown(to_reuse)) => { Ok(ParseRes::Unknown(to_reuse)) => {
if to_reuse.parsing_started() { if to_reuse.parsing_started() {
panic!("Module \"{}\" started parsing syntax, but returned Unknown!", p.name()); panic!("Module \"{}\" started parsing syntax, but returned Unknown!", p.name());

@ -151,12 +151,6 @@ pub enum BuiltinOp {
/// Yield control, optionally yielding a value that must be consumed (by reading the task handle) /// Yield control, optionally yielding a value that must be consumed (by reading the task handle)
/// before execution can resume /// before execution can resume
Yield { value: Option<Rd> }, Yield { value: Option<Rd> },
/// Set runtime option
RuntimeOpt { opt: Rd, value: Rd },
/// Begin critical section
CriticalBegin,
/// End critical section
CriticalEnd,
/// Deny jumps, skips and run across this address, producing a run-time fault. /// Deny jumps, skips and run across this address, producing a run-time fault.
Barrier { Barrier {
kind: Barrier, kind: Barrier,

@ -10,8 +10,6 @@ use crate::runtime::frame::StackFrame;
use crate::runtime::run_thread::{state::RunState, ThreadInfo}; use crate::runtime::run_thread::{state::RunState, ThreadInfo};
use crate::asm::instr::cond::Flag; use crate::asm::instr::cond::Flag;
use super::RT_OPT_TIMESLICE;
impl OpTrait for BuiltinOp { impl OpTrait for BuiltinOp {
fn execute(&self, info: &ThreadInfo, state: &mut RunState) -> Result<EvalRes, Fault> { fn execute(&self, info: &ThreadInfo, state: &mut RunState) -> Result<EvalRes, Fault> {
let program = &info.program; let program = &info.program;
@ -254,35 +252,6 @@ impl OpTrait for BuiltinOp {
BuiltinOp::Join(obj) => { BuiltinOp::Join(obj) => {
res.sched = SchedSignal::Join(obj.read(state)?); res.sched = SchedSignal::Join(obj.read(state)?);
} }
BuiltinOp::RuntimeOpt { opt, value } => {
let opt = state.read(opt)?;
let val = state.read(value)?;
match opt {
RT_OPT_TIMESLICE => {
*state.thread_info.scheduler_interval.write() = Duration::from_micros(val);
}
_ => {
warn!("Invalid rt-opt {}", opt);
state.set_flag(Flag::Invalid, true);
}
}
}
BuiltinOp::CriticalBegin => {
if state.critical_section == Value::MAX {
return Err(Fault::UnbalancedCriticalSection);
}
state.critical_section += 1;
res.cycles = 0;
}
BuiltinOp::CriticalEnd => {
if state.critical_section == 0 {
return Err(Fault::UnbalancedCriticalSection);
}
state.critical_section -= 1;
res.cycles = 0;
}
} }
Ok(res) Ok(res)

@ -4,19 +4,11 @@ use crate::asm::error::CrsnError;
use crate::asm::instr::op::OpKind; use crate::asm::instr::op::OpKind;
use crate::asm::parse::arg_parser::TokenParser; use crate::asm::parse::arg_parser::TokenParser;
use crate::module::{CrsnExtension, ParseRes}; use crate::module::{CrsnExtension, ParseRes};
use crate::asm::data::literal::Value;
use crate::asm::instr::{Flatten, Op};
use crate::asm::parse::parse_instructions;
use crate::builtin::defs::BuiltinOp;
use crate::asm::instr::flatten::jumps_to_skips;
use crate::asm::data::{Rd, RdData};
pub mod defs; pub mod defs;
pub mod exec; pub mod exec;
pub mod parse; pub mod parse;
pub(crate) const RT_OPT_TIMESLICE : u64 = 1;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct BuiltinOps; pub struct BuiltinOps;
@ -34,76 +26,4 @@ impl CrsnExtension for BuiltinOps {
fn parse_op<'a>(&self, pos: &SourcePosition, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> { fn parse_op<'a>(&self, pos: &SourcePosition, keyword: &str, args: TokenParser<'a>) -> Result<ParseRes<'a, OpKind>, CrsnError> {
parse::parse_op(pos, keyword, args) parse::parse_op(pos, keyword, args)
} }
/// Get value of an extension-provided constant.
/// This constant may be an object handle, or a constant value used as argument in some other instruction.
fn get_constant_value<'a>(&self, name: &str) -> Option<Value> {
match name {
"RT_TIMESLICE" => Some(RT_OPT_TIMESLICE),
_ => None,
}
}
/// Parse a generic S-expression (non-op) that started with the given keyword
///
/// pcx is available on the arg_tokens parser
fn parse_syntax<'a>(&self, pos: &SourcePosition, keyword: &str, tokens: TokenParser<'a>)
-> Result<ParseRes<'a, Box<dyn Flatten>>, CrsnError>
{
if keyword == "crit" || keyword == "critical" {
let pcx = tokens.pcx;
let opts = parse_instructions(tokens.into_iter(), pos, pcx)?;
let flattened = jumps_to_skips(opts.flatten(&pcx.state.borrow_mut().label_num)?)?;
let len = flattened.len();
for (n, op) in flattened.iter().enumerate() {
match op.kind {
OpKind::BuiltIn(BuiltinOp::Skip(Rd(RdData::Immediate(skip)))) => {
let signed = i64::from_ne_bytes(skip.to_ne_bytes());
let target = n as i64 + signed;
if target < 0 || target > len as i64 {
return Err(CrsnError::Parse("Cannot jump out of a critical section!".into(), op.pos()));
}
}
/* Non-constant skip cannot be validated */
OpKind::BuiltIn(BuiltinOp::Skip(_)) => {
return Err(CrsnError::Parse("Variable skips are not allowed in a critical section".into(), op.pos()));
}
/* Yield in critical makes zero sense */
OpKind::BuiltIn(BuiltinOp::Yield { .. }) => {
return Err(CrsnError::Parse("Yield in a critical section!".into(), op.pos()));
}
/* This is likely a bug */
OpKind::BuiltIn(BuiltinOp::Ret(_)) => {
return Err(CrsnError::Parse("Ret in a critical section!".into(), op.pos()));
}
/* Probably also a bug. If someone really wants this, they can start and end the critical section manually. */
OpKind::BuiltIn(BuiltinOp::FarJump(_)) => {
return Err(CrsnError::Parse("Far jump a critical section!".into(), op.pos()));
}
_ => {}
}
}
let vec : Vec<Box<dyn Flatten>> = vec![
Box::new(Op {
kind: OpKind::BuiltIn(BuiltinOp::CriticalBegin),
cond: None,
pos: pos.clone()
}),
Box::new(flattened),
Box::new(Op {
kind: OpKind::BuiltIn(BuiltinOp::CriticalEnd),
cond: None,
pos: pos.clone()
})
];
return Ok(ParseRes::Parsed(Box::new(vec)));
}
Ok(ParseRes::Unknown(tokens))
}
} }

@ -183,21 +183,6 @@ pub(crate) fn parse_op<'a>(op_pos: &SourcePosition, keyword: &str, mut args: Tok
}) })
} }
"crit-begin" => {
BuiltinOp::CriticalBegin
}
"crit-end" => {
BuiltinOp::CriticalEnd
}
"rt-opt" => {
BuiltinOp::RuntimeOpt {
opt: args.next_rd()?,
value: args.next_rd()?,
}
}
"ld" => { "ld" => {
BuiltinOp::Load { BuiltinOp::Load {
dst: args.next_wr()?, dst: args.next_wr()?,
@ -364,9 +349,6 @@ pub(crate) fn parse_routine_name(name: String, pos: &SourcePosition) -> Result<R
pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp { pub(crate) fn to_sexp(op: &BuiltinOp) -> Sexp {
match op { match op {
BuiltinOp::CriticalBegin => sexp::list(&[A("crit-begin")]),
BuiltinOp::CriticalEnd => sexp::list(&[A("crit-end")]),
BuiltinOp::RuntimeOpt { opt, value } => sexp::list(&[A("rt-opt"), A(opt), A(value)]),
BuiltinOp::Nop => sexp::list(&[A("nop")]), BuiltinOp::Nop => sexp::list(&[A("nop")]),
BuiltinOp::Halt => sexp::list(&[A("halt")]), BuiltinOp::Halt => sexp::list(&[A("halt")]),
BuiltinOp::Sleep { count: micros, unit_us: SleepUnit::Sec } => sexp::list(&[A("sslp"), A(micros)]), BuiltinOp::Sleep { count: micros, unit_us: SleepUnit::Sec } => sexp::list(&[A("sslp"), A(micros)]),
@ -612,12 +594,10 @@ mod test {
parked: Default::default(), parked: Default::default(),
global_regs: [0; REG_COUNT], global_regs: [0; REG_COUNT],
ext_data: Default::default(), ext_data: Default::default(),
cr_deadline: None, cr_deadline: None
critical_section: 0,
}, },
const_eval_ti: ti.clone(), const_eval_ti: ti.clone(),
parsing_expr: false, parsing_expr: false
label_num: Default::default()
}), }),
}; };

@ -77,15 +77,6 @@ pub enum Fault {
#[error("Attempt to read undefined extension data store")] #[error("Attempt to read undefined extension data store")]
ExtDataNotDefined, ExtDataNotDefined,
#[error("Unbalanced critical sections")]
UnbalancedCriticalSection,
#[error("Deadlock detected")]
Deadlock,
#[error("Root routine returned or yielded a value")]
RootReturned,
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]

@ -1,5 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
pub use info::ThreadInfo; pub use info::ThreadInfo;
@ -13,8 +14,6 @@ use crate::runtime::program::Program;
use crate::runtime::run_thread::state::{CoroutineContext, CoroutineState}; use crate::runtime::run_thread::state::{CoroutineContext, CoroutineState};
use std::{mem, thread}; use std::{mem, thread};
use crate::asm::instr::cond::Flag; use crate::asm::instr::cond::Flag;
use parking_lot::RwLock;
use crate::asm::instr::Flatten;
#[derive(Clone, Copy, Eq, PartialEq, Debug, Ord, PartialOrd)] #[derive(Clone, Copy, Eq, PartialEq, Debug, Ord, PartialOrd)]
pub struct ThreadToken(pub u32); pub struct ThreadToken(pub u32);
@ -41,14 +40,12 @@ impl RunThread {
pub fn new(params: ThreadParams) -> Self { pub fn new(params: ThreadParams) -> Self {
let extensions = params.program.extensions.clone(); let extensions = params.program.extensions.clone();
// TODO investigate if this division to 2 structs is still needed
let ti = Arc::new(ThreadInfo { let ti = Arc::new(ThreadInfo {
id: params.id, id: params.id,
uniq: params.uniq, uniq: params.uniq,
program: params.program, program: params.program,
cycle_time: params.cycle_time, cycle_time: params.cycle_time,
scheduler_interval: RwLock::new(params.scheduler_interval), scheduler_interval: params.scheduler_interval,
extensions, extensions,
}); });
@ -63,8 +60,7 @@ impl RunThread {
parked: Default::default(), parked: Default::default(),
global_regs: [0; REG_COUNT], global_regs: [0; REG_COUNT],
ext_data: Default::default(), ext_data: Default::default(),
cr_deadline: None, cr_deadline: None
critical_section: 0,
}; };
Self { Self {
@ -73,17 +69,21 @@ impl RunThread {
} }
} }
/// Spawn as a thread
pub fn spawn(self) -> JoinHandle<()> {
std::thread::spawn(move || {
self.run();
})
}
/// Start synchronously /// Start synchronously
pub fn run(mut self) { pub fn run(mut self) {
let mut loop_helper = spin_sleep::LoopHelper::builder() let mut loop_helper = spin_sleep::LoopHelper::builder()
.build_with_target_rate(1.0/self.info.cycle_time.as_secs_f64()); .build_with_target_rate(1.0/self.info.cycle_time.as_secs_f64());
loop_helper.loop_start(); loop_helper.loop_start();
let mut orig_pc; 'run: loop {
let fault = 'run: loop {
let mut want_switch = false; let mut want_switch = false;
orig_pc = self.state.cr.frame.pc;
match self.eval_op() { match self.eval_op() {
Ok(EvalRes { cycles, advance, sched }) => { Ok(EvalRes { cycles, advance, sched }) => {
for _ in 0..cycles { for _ in 0..cycles {
@ -91,6 +91,7 @@ impl RunThread {
loop_helper.loop_start(); loop_helper.loop_start();
} }
trace!("Step {}; Status = {}", advance, self.state.cr.frame.status); trace!("Step {}; Status = {}", advance, self.state.cr.frame.status);
let orig_pc = self.state.cr.frame.pc;
self.state.cr.frame.pc.advance(advance); self.state.cr.frame.pc.advance(advance);
if let Some(dl) = self.state.cr_deadline { if let Some(dl) = self.state.cr_deadline {
@ -156,127 +157,80 @@ impl RunThread {
// Do not advance, the last instruction will be re-tried // Do not advance, the last instruction will be re-tried
want_switch = true; want_switch = true;
} }
Err(Fault::Halt) => {
// TODO implement coordinated shutdown when more threads are running!
break 'run;
}
Err(e) => { Err(e) => {
break 'run e; error!("Fault: {:?}", e);
error!("Core dump: {:?}", self.state);
break 'run;
} }
} }
// Resolve the next coroutine to run, or wait a bit... if want_switch {
'next: loop { trace!("Switch requested");
if want_switch {
let now = Instant::now();
if self.state.critical_section > 0 { let now = Instant::now();
match self.state.cr.cr_state {
CoroutineState::Ready => {}
CoroutineState::Sleep { due } => {
let time = due.saturating_duration_since(now);
trace!("Sleep in critical: {:?}", time);
thread::sleep(time);
continue 'run;
}
CoroutineState::Finished(_) | CoroutineState::YieldedValue(_) => {
// This is not good
break 'run Fault::Deadlock;
}
}
// This is either a bug, or waiting for IO. let mut candidate = None;
// If it is the latter, try to give the OS a bit of breathing room.. let mut closest_due = None;
std::thread::yield_now(); for _ in 0..self.state.parked.len() {
continue 'run; if let Some(mut rt) = self.state.parked.pop_front() {
} match rt.cr_state {
CoroutineState::Ready => {
trace!("Switch requested"); candidate = Some(rt);
break;
let mut candidate = None; }
let mut closest_due = None; CoroutineState::Sleep { due } => {
for _ in 0..self.state.parked.len() { if due <= now {
if let Some(mut rt) = self.state.parked.pop_front() { rt.cr_state = CoroutineState::Ready;
match rt.cr_state {
CoroutineState::Ready => {
candidate = Some(rt); candidate = Some(rt);
break; break;
} } else {
CoroutineState::Sleep { due } => { match closest_due {
if due <= now { Some(d) => {
rt.cr_state = CoroutineState::Ready; if d > due {
candidate = Some(rt);
break;
} else {
match closest_due {
Some(d) => {
if d > due {
closest_due = Some(due);
}
},
None => {
closest_due = Some(due); closest_due = Some(due);
} }
},
None => {
closest_due = Some(due);
} }
self.state.parked.push_back(rt);
} }
}
_ => {
self.state.parked.push_back(rt); self.state.parked.push_back(rt);
} }
} }
_ => {
self.state.parked.push_back(rt);
}
} }
} }
}
if let Some(cr) = candidate { if let Some(cr) = candidate {
trace!("Context switch to {:?}", cr); trace!("Context switch to {:?}", cr);
// Do switch // Do switch
let old = mem::replace(&mut self.state.cr, cr); let old = mem::replace(&mut self.state.cr, cr);
self.state.parked.push_back(old); self.state.parked.push_back(old);
self.state.cr_deadline = Some(now + self.info.scheduler_interval);
self.state.start_task_switching(); } else if let Some(due) = closest_due {
let time = due.saturating_duration_since(now);
} else if let Some(due) = closest_due { trace!("No thread to switch to, sleep {:?}", time);
let time = due.saturating_duration_since(now); thread::sleep(time);
trace!("No thread to switch to, sleep {:?}", time);
thread::sleep(time);
continue 'next;
} else {
// Nothing to run?
thread::yield_now();
trace!("No thread to switch to!");
}
let n_alive = self.state.parked.iter()
.filter(|p| p.cr_state.is_alive()).count();
if n_alive == 0 {
trace!("Stop task switching, no parked threads are alive");
self.state.stop_task_switching(); // This should improve performance in single-threaded mode
}
match self.state.cr.cr_state {
CoroutineState::Finished(_) | CoroutineState::YieldedValue(_) => {
break 'run Fault::RootReturned;
}
_ => {}
}
} }
break 'next; let n_alive = self.state.parked.iter()
} .filter(|p| p.cr_state.is_alive()).count();
};
match fault { if n_alive == 0 {
Fault::Halt => { trace!("Stop task switching, no parked threads are alive");
debug!("Thread ended."); self.state.cr_deadline = None;
}
e => {
if let Some(instr) = self.info.program.ops.get(orig_pc.0 as usize) {
error!("Fault at {}: {}", instr.pos(), e);
} else {
error!("Fault at PC {}: {}", orig_pc, e);
} }
warn!("Core dump: {:?}", self.state);
} }
} }
debug!("Thread ended.");
} }
} }

@ -6,7 +6,6 @@ use crate::asm::data::literal::Value;
use crate::runtime::program::Program; use crate::runtime::program::Program;
use crate::runtime::run_thread::ThreadToken; use crate::runtime::run_thread::ThreadToken;
use crate::module::{CrsnExtension, CrsnUniq}; use crate::module::{CrsnExtension, CrsnUniq};
use parking_lot::RwLock;
#[derive(Debug)] #[derive(Debug)]
pub struct ThreadInfo { pub struct ThreadInfo {
@ -19,7 +18,7 @@ pub struct ThreadInfo {
/// Program to run /// Program to run
pub(crate) cycle_time: Duration, pub(crate) cycle_time: Duration,
/// Interval one thread/coroutine is let to run before the context switches /// Interval one thread/coroutine is let to run before the context switches
pub(crate) scheduler_interval: RwLock<Duration>, pub(crate) scheduler_interval: Duration,
/// Extensions /// Extensions
pub extensions: Arc<Vec<Box<dyn CrsnExtension>>>, pub extensions: Arc<Vec<Box<dyn CrsnExtension>>>,
} }

@ -12,7 +12,7 @@ use nudge::{likely};
use crate::asm::instr::cond::Flag; use crate::asm::instr::cond::Flag;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::fmt; use std::fmt;
use std::time::{Instant, Duration}; use std::time::Instant;
pub struct RunState { pub struct RunState {
pub thread_info: Arc<ThreadInfo>, pub thread_info: Arc<ThreadInfo>,
@ -26,8 +26,6 @@ pub struct RunState {
pub ext_data: ExtensionDataStore, pub ext_data: ExtensionDataStore,
/// Execution deadline, if multi-tasked /// Execution deadline, if multi-tasked
pub cr_deadline: Option<Instant>, pub cr_deadline: Option<Instant>,
/// Nonzero if inside a critical section
pub critical_section: Value,
} }
#[derive(Debug,Default)] #[derive(Debug,Default)]
@ -95,24 +93,10 @@ impl RunState {
}); });
if self.cr_deadline.is_none() { if self.cr_deadline.is_none() {
// start context switching // start context switching
self.start_task_switching(); self.cr_deadline = Some(Instant::now() + self.thread_info.scheduler_interval);
} }
handle handle
} }
pub(crate) fn start_task_switching(&mut self) {
let ival = *self.thread_info.scheduler_interval.read();
if ival > Duration::default() {
self.cr_deadline = Some(Instant::now() + ival);
} else {
// Disabled
self.cr_deadline = None;
}
}
pub(crate) fn stop_task_switching(&mut self) {
self.cr_deadline = None;
}
} }
impl Debug for RunState { impl Debug for RunState {

@ -1,44 +0,0 @@
(
; This example shows the use of critical sections.
; Set short timeslice (50us) to make the effect more pronounced
(rt-opt RT_TIMESLICE 50)
(spawn _ unsafe 'A' 'Z')
(spawn _ unsafe 'a' 'z')
(spawn _ safe '0' '9') ; Notice that the sequence 0-9 is always printed in its entirety - because it is in a critical section
(msleep 200)
(halt)
(proc unsafe start end
; This can be interrupted any time
(:x)
(yield)
(ld r0 start)
(:l)
(ld @cout r0)
(cmp r0 end)
(j.eq :x)
(inc r0)
(j :l)
)
(proc safe start end
(:again)
(ld r0 start)
; The sequence will always be complete
(crit
(ld @cout ' ') ; space to make it easier to read
(:l)
(ld @cout r0)
(cmp r0 end)
(j.eq :x)
(inc r0)
(j :l)
(:x)
(ld @cout ' ') ; space to make it easier to read
)
(yield)
(j :again)
)
)
Loading…
Cancel
Save