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.
164 lines
5.6 KiB
164 lines
5.6 KiB
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
|
|
use sexp::SourcePosition;
|
|
|
|
use crate::asm::data::literal::{Addr, Label, RoutineName};
|
|
use crate::asm::error::CrsnError;
|
|
use crate::asm::instr::Op;
|
|
use crate::asm::instr::op::OpKind;
|
|
use crate::builtin::defs::{Barrier, BuiltinOp};
|
|
use crate::module::CrsnExtension;
|
|
use crate::runtime::fault::Fault;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Debug)]
|
|
pub struct Program {
|
|
pub ops: Vec<Op>,
|
|
pub extensions: Arc<Vec<Box<dyn CrsnExtension>>>,
|
|
routines: HashMap<RoutineName, Addr>,
|
|
far_labels: HashMap<Label, Addr>,
|
|
/// Barriers from-to (inclusive).
|
|
/// Standalone barriers have both addresses the same.
|
|
barriers: Vec<(Addr, Addr)>,
|
|
/// This is used at runtime to better report error locations when included files are used
|
|
pub(crate) file_names: Vec<PathBuf>,
|
|
}
|
|
|
|
impl Program {
|
|
pub fn new(
|
|
ops: Vec<Op>,
|
|
extensions: Arc<Vec<Box<dyn CrsnExtension>>>,
|
|
file_names: Vec<PathBuf>,
|
|
) -> Result<Arc<Self>, CrsnError> {
|
|
let mut p = Self {
|
|
ops,
|
|
extensions,
|
|
routines: Default::default(),
|
|
far_labels: Default::default(),
|
|
barriers: Default::default(),
|
|
file_names,
|
|
};
|
|
p.scan()?;
|
|
Ok(Arc::new(p))
|
|
}
|
|
|
|
/// Find all the named things
|
|
fn scan(&mut self) -> Result<(), CrsnError> {
|
|
let mut barrier_starts: HashMap<&Label, Addr> = HashMap::new();
|
|
for (pos, op) in self.ops.iter().enumerate() {
|
|
match &op.kind {
|
|
OpKind::BuiltIn(BuiltinOp::FarLabel(name)) => {
|
|
self.far_labels.insert(name.clone(), pos.into());
|
|
}
|
|
OpKind::BuiltIn(BuiltinOp::Routine(name)) => {
|
|
self.routines.insert(name.clone(), pos.into());
|
|
}
|
|
OpKind::BuiltIn(
|
|
BuiltinOp::Barrier {
|
|
kind: Barrier::Open(lbl), ..
|
|
}
|
|
) => {
|
|
barrier_starts.insert(lbl, pos.into());
|
|
}
|
|
OpKind::BuiltIn(
|
|
BuiltinOp::Barrier {
|
|
kind: Barrier::Close(lbl),
|
|
msg,
|
|
}
|
|
) => {
|
|
if let Some(start_pos) = barrier_starts.remove(lbl) {
|
|
self.barriers.push((start_pos, pos.into()));
|
|
self.far_labels.insert(lbl.clone(), pos.into());
|
|
} else {
|
|
return Err(CrsnError::Parse(format!("Block barrier \"{:?}\" closed without being open!", msg).into(), op.pos.clone()));
|
|
}
|
|
}
|
|
OpKind::BuiltIn(
|
|
BuiltinOp::Barrier {
|
|
kind: Barrier::Standalone,
|
|
..
|
|
}
|
|
) => {
|
|
self.barriers.push((pos.into(), pos.into()));
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
if !barrier_starts.is_empty() {
|
|
return Err(CrsnError::Parse(format!("Block barrier open without being closed: {}",
|
|
barrier_starts.iter().next().unwrap().0).into(),
|
|
Default::default()));
|
|
}
|
|
|
|
trace!("Program scanned: {:?}", self);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Read a program instruction at address
|
|
pub fn fetch_instr(&self, addr: Addr) -> &Op {
|
|
if (addr.0 as usize) < self.ops.len() {
|
|
unsafe { self.ops.get_unchecked(addr.0 as usize) }
|
|
} else {
|
|
&Op {
|
|
kind: OpKind::BuiltIn(BuiltinOp::Halt),
|
|
pos: SourcePosition {
|
|
line: 0,
|
|
column: 0,
|
|
index: 0,
|
|
file: 0,
|
|
},
|
|
cond: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Return error if any barrier is crossed during a jump / skip
|
|
pub fn validate_jump(&self, mut from: Addr, mut to: Addr) -> Result<(), Fault> {
|
|
if from > to {
|
|
std::mem::swap(&mut from, &mut to);
|
|
}
|
|
|
|
for (b0, b1) in &self.barriers {
|
|
if b0 != b1 {
|
|
// block barrier that only partially intersects the jump
|
|
if (*b0 >= from && *b0 <= to) != (*b1 >= from && *b1 <= to) {
|
|
if let OpKind::BuiltIn(BuiltinOp::Barrier { msg, .. }) = &self.fetch_instr(*b0).kind {
|
|
return Err(Fault::JumpThroughBarrier {
|
|
msg: msg.clone().unwrap_or("BLOCK BARRIER".into())
|
|
});
|
|
} else {
|
|
unreachable!();
|
|
}
|
|
}
|
|
} else {
|
|
// point barrier
|
|
if *b0 >= from && *b0 <= to {
|
|
if let OpKind::BuiltIn(BuiltinOp::Barrier { msg, .. }) = &self.fetch_instr(*b0).kind {
|
|
return Err(Fault::JumpThroughBarrier {
|
|
msg: msg.clone().unwrap_or("POINT BARRIER".into())
|
|
});
|
|
} else {
|
|
unreachable!();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Find routine by name
|
|
pub fn find_routine(&self, name: &RoutineName) -> Result<Addr, Fault> {
|
|
self.routines.get(name).copied()
|
|
.ok_or_else(|| Fault::NoSuchRoutine { routine: name.clone() })
|
|
}
|
|
|
|
/// Find far label by name
|
|
pub fn find_far_label(&self, name: &Label) -> Result<Addr, Fault> {
|
|
self.far_labels.get(name).copied()
|
|
.ok_or_else(|| Fault::NoSuchLabel { label: name.clone() })
|
|
}
|
|
}
|
|
|
|
|