pub mod opcodes;
pub mod pattern;
use std::sync::{Arc, Mutex};
use crate::error::ExecError;
use crate::io::IoSink;
use crate::module::{Module, ModuleRegistry};
use crate::native::code_management_bifs::CodeManagementFacility;
use crate::native::links::LinkFacility;
use crate::native::spawn::SpawnFacility;
use crate::native::supervision::SupervisionFacility;
use crate::process::{CodePosition, ExitReason, Process};
use crate::timer::TimerWheel;
pub struct NativeServices {
pub timers: Option<Arc<Mutex<TimerWheel>>>,
pub spawn_facility: Option<Arc<dyn SpawnFacility>>,
pub link_facility: Option<Arc<dyn LinkFacility>>,
pub supervision_facility: Option<Arc<dyn SupervisionFacility>>,
pub io_sink: Option<Arc<dyn IoSink>>,
pub code_management_facility: Option<Arc<dyn CodeManagementFacility>>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ExecutionResult {
Yielded,
Waiting,
Exited(ExitReason),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum InstructionOutcome {
Continue,
Jump(CodePosition),
NativeContinuation,
Yield,
Waiting,
Exit(ExitReason),
OnLoadComplete,
}
pub fn run(process: &mut Process, module: &Module) -> Result<ExecutionResult, ExecError> {
let empty = NativeServices {
timers: None,
spawn_facility: None,
link_facility: None,
supervision_facility: None,
io_sink: None,
code_management_facility: None,
};
run_loop(process, module, None, &empty)
}
pub fn run_with_registry(
process: &mut Process,
initial_module: &Module,
registry: &ModuleRegistry,
) -> Result<ExecutionResult, ExecError> {
let empty = NativeServices {
timers: None,
spawn_facility: None,
link_facility: None,
supervision_facility: None,
io_sink: None,
code_management_facility: None,
};
run_loop(process, initial_module, Some(registry), &empty)
}
pub fn run_with_timer_services(
process: &mut Process,
initial_module: &Module,
timers: Arc<Mutex<TimerWheel>>,
) -> Result<ExecutionResult, ExecError> {
let services = NativeServices {
timers: Some(timers),
spawn_facility: None,
link_facility: None,
supervision_facility: None,
io_sink: None,
code_management_facility: None,
};
run_loop(process, initial_module, None, &services)
}
pub fn run_with_native_services(
process: &mut Process,
initial_module: &Module,
registry: &ModuleRegistry,
services: &NativeServices,
) -> Result<ExecutionResult, ExecError> {
run_loop(process, initial_module, Some(registry), services)
}
fn current_module_for_position(
process: &mut Process,
position: CodePosition,
initial_module: &Module,
registry: Option<&ModuleRegistry>,
) -> Result<Arc<Module>, ExecError> {
if let Some(current) = process.current_module()
&& current.name == position.module
{
return Ok(Arc::clone(current));
}
let module = registry
.and_then(|registry| registry.lookup(position.module))
.or_else(|| {
(initial_module.name == position.module).then(|| Arc::new(initial_module.clone()))
})
.ok_or(ExecError::InvalidOperand("code position module"))?;
process.set_current_module(Arc::clone(&module));
Ok(module)
}
fn run_loop(
process: &mut Process,
initial_module: &Module,
registry: Option<&ModuleRegistry>,
services: &NativeServices,
) -> Result<ExecutionResult, ExecError> {
if process.code_position().is_none() {
process.set_code_position(Some(CodePosition {
module: initial_module.name,
instruction_pointer: 0,
}));
}
loop {
let position = process
.code_position()
.ok_or(ExecError::InvalidOperand("code position"))?;
let module_arc = current_module_for_position(process, position, initial_module, registry)?;
let module = module_arc.as_ref();
let instruction = module
.code
.get(position.instruction_pointer)
.ok_or(ExecError::InvalidOperand("instruction pointer"))?;
let next_ip = position
.instruction_pointer
.checked_add(1)
.ok_or(ExecError::InvalidOperand("instruction pointer"))?;
match opcodes::dispatch_with_services(
process,
module,
instruction,
next_ip,
services,
registry,
)? {
InstructionOutcome::Continue => process.set_code_position(Some(CodePosition {
module: module.name,
instruction_pointer: next_ip,
})),
InstructionOutcome::NativeContinuation => {}
InstructionOutcome::Jump(target) => process.set_code_position(Some(target)),
InstructionOutcome::Yield => return Ok(ExecutionResult::Yielded),
InstructionOutcome::Waiting => return Ok(ExecutionResult::Waiting),
InstructionOutcome::Exit(reason) => {
process.set_code_position(None);
process.clear_current_module();
return Ok(ExecutionResult::Exited(reason));
}
InstructionOutcome::OnLoadComplete => {
process.set_code_position(None);
process.clear_current_module();
return Ok(ExecutionResult::Exited(ExitReason::Normal));
}
}
}
}
#[cfg(test)]
mod tests;