use sim_kernel::ContentId;
use sim_lib_control::{CleanupStack, Unwind, WorkLimit};
use crate::{
AdmissionPolicy, CodeCursor, Frame, FrameStack, FrameStackError, InstructionPolicy,
MachineDescription, MachinePermit, ManagedRootSource, RootScanError, RootSnapshot,
SourceLocation, ValueWidthPolicy,
};
pub trait MachineFrame {
fn cursor(&self) -> CodeCursor;
fn set_cursor(&mut self, cursor: CodeCursor);
}
impl<P, K, R, H> MachineFrame for Frame<P, K, R, H>
where
P: ValueWidthPolicy,
{
fn cursor(&self) -> CodeCursor {
self.cursor()
}
fn set_cursor(&mut self, cursor: CodeCursor) {
self.set_cursor(cursor);
}
}
pub enum StepOutcome<F, R, A, Y, I> {
Continue(CodeCursor),
Call(F),
Return(R),
Raise(A),
Yield(Y),
Interrupt(I),
}
pub type PolicyStep<F, R, A, Y, I, E> = Result<StepOutcome<F, R, A, Y, I>, E>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StepKind {
Continue,
Call,
Return,
Raise,
Yield,
Interrupt,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorkReceipt<Id> {
steps: Vec<(Id, StepKind)>,
}
impl<Id> WorkReceipt<Id> {
pub fn charged(&self) -> usize {
self.steps.len()
}
pub fn steps(&self) -> &[(Id, StepKind)] {
&self.steps
}
pub fn append(&mut self, later: Self) {
self.steps.extend(later.steps);
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MachineAbrupt<A, I> {
Raise(A),
Interrupt(I),
BudgetExhausted,
Fault,
}
pub type MachineUnwind<R, A, I> = Unwind<R, (), (), MachineAbrupt<A, I>>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ContinuationEvidence<Id> {
content_id: ContentId,
receipt: WorkReceipt<Id>,
}
impl<Id> ContinuationEvidence<Id> {
pub fn content_id(&self) -> &ContentId {
&self.content_id
}
pub fn receipt(&self) -> &WorkReceipt<Id> {
&self.receipt
}
}
#[derive(Debug)]
pub struct MachineCheckpoint<F, Id> {
frames: FrameStack<F>,
evidence: ContinuationEvidence<Id>,
}
impl<F, Id> MachineCheckpoint<F, Id> {
pub fn new(frames: FrameStack<F>, permit: &MachinePermit, receipt: WorkReceipt<Id>) -> Self {
Self {
frames,
evidence: ContinuationEvidence {
content_id: permit.content_id().clone(),
receipt,
},
}
}
pub fn evidence(&self) -> &ContinuationEvidence<Id> {
&self.evidence
}
pub fn resume(self, permit: &MachinePermit) -> Result<(FrameStack<F>, WorkReceipt<Id>), Self> {
if self.evidence.content_id == *permit.content_id() {
Ok((self.frames, self.evidence.receipt))
} else {
Err(self)
}
}
}
pub trait InstructionDriverPolicy<P, F>
where
P: InstructionPolicy,
{
type Return;
type Abrupt;
type Yield;
type Interrupt;
type Fault;
#[allow(
clippy::type_complexity,
reason = "the explicit outcome channels are the driver contract"
)]
fn step(
&mut self,
instruction: &P::Instruction,
frame: &mut F,
) -> PolicyStep<F, Self::Return, Self::Abrupt, Self::Yield, Self::Interrupt, Self::Fault>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LocatedFault<Id, E> {
pub instruction: Id,
pub location: SourceLocation,
pub fault: E,
}
pub enum DriveOutcome<Id, R, A, Y, I> {
Continue(WorkReceipt<Id>),
Return(R, WorkReceipt<Id>),
Raise(A, WorkReceipt<Id>),
Yield(Y, WorkReceipt<Id>),
Interrupt(I, WorkReceipt<Id>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DriveError<Id, E> {
PermitMismatch,
EmptyFrames,
FrameLimit(FrameStackError),
Fault(LocatedFault<Id, E>),
}
pub type DriveResult<Id, R, A, Y, I, E> = Result<DriveOutcome<Id, R, A, Y, I>, DriveError<Id, E>>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SafepointDriveError<Id, E, S> {
RootScan(RootScanError),
Safepoint(S),
Drive(DriveError<Id, E>),
}
pub struct Driver<D> {
policy: D,
}
impl<D> Driver<D> {
pub fn new(policy: D) -> Self {
Self { policy }
}
#[allow(
clippy::type_complexity,
reason = "the explicit outcome channels are the driver contract"
)]
pub fn drive<P, M, A, F>(
&mut self,
description: &MachineDescription<'_, P, M>,
permit: &MachinePermit,
frames: &mut FrameStack<F>,
work: WorkLimit,
) -> DriveResult<P::InstructionId, D::Return, D::Abrupt, D::Yield, D::Interrupt, D::Fault>
where
P: InstructionPolicy,
P::InstructionId: Copy + Eq + Ord,
A: AdmissionPolicy<P, M>,
F: MachineFrame,
D: InstructionDriverPolicy<P, F>,
{
if !permit.accepts::<P, M, A>(description) {
return Err(DriveError::PermitMismatch);
}
if frames.current().is_none() {
return Err(DriveError::EmptyFrames);
}
let mut steps = Vec::with_capacity(work.0);
for _ in 0..work.0 {
let cursor = frames.current().ok_or(DriveError::EmptyFrames)?.cursor();
let located = description.code().instruction(cursor);
let id = *located.id();
let location = located.location().clone();
let outcome = self
.policy
.step(
located.instruction(),
frames.current_mut().expect("frame was checked"),
)
.map_err(|fault| {
DriveError::Fault(LocatedFault {
instruction: id,
location,
fault,
})
})?;
match outcome {
StepOutcome::Continue(next) => {
steps.push((id, StepKind::Continue));
frames
.current_mut()
.expect("current frame remains")
.set_cursor(next);
}
StepOutcome::Call(frame) => {
steps.push((id, StepKind::Call));
frames.push(frame).map_err(DriveError::FrameLimit)?;
}
StepOutcome::Return(value) => {
steps.push((id, StepKind::Return));
frames.pop();
if frames.current().is_none() {
return Ok(DriveOutcome::Return(value, WorkReceipt { steps }));
}
}
StepOutcome::Raise(value) => {
steps.push((id, StepKind::Raise));
return Ok(DriveOutcome::Raise(value, WorkReceipt { steps }));
}
StepOutcome::Yield(value) => {
steps.push((id, StepKind::Yield));
return Ok(DriveOutcome::Yield(value, WorkReceipt { steps }));
}
StepOutcome::Interrupt(value) => {
steps.push((id, StepKind::Interrupt));
return Ok(DriveOutcome::Interrupt(value, WorkReceipt { steps }));
}
}
}
Ok(DriveOutcome::Continue(WorkReceipt { steps }))
}
#[allow(
clippy::type_complexity,
reason = "the explicit outcome channels are the driver contract"
)]
pub fn drive_with_safepoints<P, M, A, F, S>(
&mut self,
description: &MachineDescription<'_, P, M>,
permit: &MachinePermit,
frames: &mut FrameStack<F>,
work: WorkLimit,
root_budget: WorkLimit,
mut observe: impl FnMut(&RootSnapshot) -> Result<(), S>,
) -> Result<
DriveOutcome<P::InstructionId, D::Return, D::Abrupt, D::Yield, D::Interrupt>,
SafepointDriveError<P::InstructionId, D::Fault, S>,
>
where
P: InstructionPolicy,
P::InstructionId: Copy + Eq + Ord,
A: AdmissionPolicy<P, M>,
F: MachineFrame + ManagedRootSource,
D: InstructionDriverPolicy<P, F>,
{
if !permit.accepts::<P, M, A>(description) {
return Err(SafepointDriveError::Drive(DriveError::PermitMismatch));
}
if frames.current().is_none() {
return Err(SafepointDriveError::Drive(DriveError::EmptyFrames));
}
let mut combined = WorkReceipt { steps: Vec::new() };
for _ in 0..work.0 {
let cursor = frames
.current()
.ok_or(SafepointDriveError::Drive(DriveError::EmptyFrames))?
.cursor();
if description.code().instruction(cursor).is_safepoint() {
let roots = RootSnapshot::scan(frames, root_budget)
.map_err(SafepointDriveError::RootScan)?;
observe(&roots).map_err(SafepointDriveError::Safepoint)?;
}
let outcome = self
.drive::<P, M, A, F>(description, permit, frames, WorkLimit(1))
.map_err(SafepointDriveError::Drive)?;
match outcome {
DriveOutcome::Continue(receipt) => combined.append(receipt),
DriveOutcome::Return(value, receipt) => {
combined.append(receipt);
return Ok(DriveOutcome::Return(value, combined));
}
DriveOutcome::Raise(value, receipt) => {
combined.append(receipt);
return Ok(DriveOutcome::Raise(value, combined));
}
DriveOutcome::Yield(value, receipt) => {
combined.append(receipt);
return Ok(DriveOutcome::Yield(value, combined));
}
DriveOutcome::Interrupt(value, receipt) => {
combined.append(receipt);
return Ok(DriveOutcome::Interrupt(value, combined));
}
}
}
Ok(DriveOutcome::Continue(combined))
}
#[allow(
clippy::type_complexity,
reason = "matches the explicit driver channels"
)]
pub fn drive_protected<P, M, A, F>(
&mut self,
description: &MachineDescription<'_, P, M>,
permit: &MachinePermit,
frames: &mut FrameStack<F>,
work: WorkLimit,
) -> DriveResult<P::InstructionId, D::Return, D::Abrupt, D::Yield, D::Interrupt, D::Fault>
where
P: InstructionPolicy,
P::InstructionId: Copy + Eq + Ord,
A: AdmissionPolicy<P, M>,
F: MachineFrame,
D: InstructionDriverPolicy<P, F>,
{
let mut combined = WorkReceipt { steps: Vec::new() };
let mut left = work.0;
while left > 0 {
let cursor = frames.current().ok_or(DriveError::EmptyFrames)?.cursor();
let outcome = self.drive::<P, M, A, F>(description, permit, frames, WorkLimit(1))?;
match outcome {
DriveOutcome::Raise(value, receipt) => {
left -= receipt.charged();
combined.append(receipt);
if let Some(region) = description.code().innermost_protected_region(cursor) {
frames
.current_mut()
.expect("raising frame remains")
.set_cursor(region.handler);
} else {
return Ok(DriveOutcome::Raise(value, combined));
}
}
DriveOutcome::Continue(receipt) => {
left -= receipt.charged();
combined.append(receipt);
if left == 0 {
return Ok(DriveOutcome::Continue(combined));
}
}
DriveOutcome::Return(value, receipt) => {
combined.append(receipt);
return Ok(DriveOutcome::Return(value, combined));
}
DriveOutcome::Yield(value, receipt) => {
combined.append(receipt);
return Ok(DriveOutcome::Yield(value, combined));
}
DriveOutcome::Interrupt(value, receipt) => {
combined.append(receipt);
return Ok(DriveOutcome::Interrupt(value, combined));
}
}
}
Ok(DriveOutcome::Continue(combined))
}
#[allow(
clippy::type_complexity,
reason = "matches the explicit driver channels"
)]
pub fn drive_with_cleanup<P, M, A, F>(
&mut self,
description: &MachineDescription<'_, P, M>,
permit: &MachinePermit,
frames: &mut FrameStack<F>,
work: WorkLimit,
cleanups: CleanupStack<MachineUnwind<D::Return, D::Abrupt, D::Interrupt>>,
) -> DriveResult<P::InstructionId, D::Return, D::Abrupt, D::Yield, D::Interrupt, D::Fault>
where
P: InstructionPolicy,
P::InstructionId: Copy + Eq + Ord,
A: AdmissionPolicy<P, M>,
F: MachineFrame,
D: InstructionDriverPolicy<P, F>,
D::Return: Clone,
D::Abrupt: Clone,
D::Interrupt: Clone,
{
let outcome = match self.drive_protected::<P, M, A, F>(description, permit, frames, work) {
Ok(outcome) => outcome,
Err(error) => {
cleanups.unwind(Unwind::Exception(MachineAbrupt::Fault));
return Err(error);
}
};
match &outcome {
DriveOutcome::Return(value, _) => {
cleanups.unwind(Unwind::Return(value.clone()));
}
DriveOutcome::Raise(value, _) => {
cleanups.unwind(Unwind::Exception(MachineAbrupt::Raise(value.clone())));
}
DriveOutcome::Interrupt(value, _) => {
cleanups.unwind(Unwind::Exception(MachineAbrupt::Interrupt(value.clone())));
}
DriveOutcome::Continue(_) => {
cleanups.unwind(Unwind::Exception(MachineAbrupt::BudgetExhausted));
}
DriveOutcome::Yield(_, _) => return Ok(outcome),
}
Ok(outcome)
}
}