1use sim_kernel::ContentId;
2use sim_lib_control::{CleanupStack, Unwind, WorkLimit};
3
4use crate::{
5 AdmissionPolicy, CodeCursor, Frame, FrameStack, FrameStackError, InstructionPolicy,
6 MachineDescription, MachinePermit, ManagedRootSource, RootScanError, RootSnapshot,
7 SourceLocation, ValueWidthPolicy,
8};
9
10pub trait MachineFrame {
14 fn cursor(&self) -> CodeCursor;
16
17 fn set_cursor(&mut self, cursor: CodeCursor);
19}
20
21impl<P, K, R, H> MachineFrame for Frame<P, K, R, H>
22where
23 P: ValueWidthPolicy,
24{
25 fn cursor(&self) -> CodeCursor {
26 self.cursor()
27 }
28
29 fn set_cursor(&mut self, cursor: CodeCursor) {
30 self.set_cursor(cursor);
31 }
32}
33
34pub enum StepOutcome<F, R, A, Y, I> {
36 Continue(CodeCursor),
38 Call(F),
40 Return(R),
42 Raise(A),
44 Yield(Y),
46 Interrupt(I),
48}
49
50pub type PolicyStep<F, R, A, Y, I, E> = Result<StepOutcome<F, R, A, Y, I>, E>;
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum StepKind {
56 Continue,
58 Call,
60 Return,
62 Raise,
64 Yield,
66 Interrupt,
68}
69
70#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct WorkReceipt<Id> {
73 steps: Vec<(Id, StepKind)>,
74}
75
76impl<Id> WorkReceipt<Id> {
77 pub fn charged(&self) -> usize {
79 self.steps.len()
80 }
81
82 pub fn steps(&self) -> &[(Id, StepKind)] {
84 &self.steps
85 }
86
87 pub fn append(&mut self, later: Self) {
89 self.steps.extend(later.steps);
90 }
91}
92
93#[derive(Clone, Debug, PartialEq, Eq)]
95pub enum MachineAbrupt<A, I> {
96 Raise(A),
98 Interrupt(I),
100 BudgetExhausted,
102 Fault,
104}
105
106pub type MachineUnwind<R, A, I> = Unwind<R, (), (), MachineAbrupt<A, I>>;
108
109#[derive(Clone, Debug, PartialEq, Eq)]
111pub struct ContinuationEvidence<Id> {
112 content_id: ContentId,
113 receipt: WorkReceipt<Id>,
114}
115
116impl<Id> ContinuationEvidence<Id> {
117 pub fn content_id(&self) -> &ContentId {
119 &self.content_id
120 }
121
122 pub fn receipt(&self) -> &WorkReceipt<Id> {
124 &self.receipt
125 }
126}
127
128#[derive(Debug)]
130pub struct MachineCheckpoint<F, Id> {
131 frames: FrameStack<F>,
132 evidence: ContinuationEvidence<Id>,
133}
134
135impl<F, Id> MachineCheckpoint<F, Id> {
136 pub fn new(frames: FrameStack<F>, permit: &MachinePermit, receipt: WorkReceipt<Id>) -> Self {
138 Self {
139 frames,
140 evidence: ContinuationEvidence {
141 content_id: permit.content_id().clone(),
142 receipt,
143 },
144 }
145 }
146
147 pub fn evidence(&self) -> &ContinuationEvidence<Id> {
149 &self.evidence
150 }
151
152 pub fn resume(self, permit: &MachinePermit) -> Result<(FrameStack<F>, WorkReceipt<Id>), Self> {
154 if self.evidence.content_id == *permit.content_id() {
155 Ok((self.frames, self.evidence.receipt))
156 } else {
157 Err(self)
158 }
159 }
160}
161
162pub trait InstructionDriverPolicy<P, F>
167where
168 P: InstructionPolicy,
169{
170 type Return;
172 type Abrupt;
174 type Yield;
176 type Interrupt;
178 type Fault;
180
181 #[allow(
183 clippy::type_complexity,
184 reason = "the explicit outcome channels are the driver contract"
185 )]
186 fn step(
187 &mut self,
188 instruction: &P::Instruction,
189 frame: &mut F,
190 ) -> PolicyStep<F, Self::Return, Self::Abrupt, Self::Yield, Self::Interrupt, Self::Fault>;
191}
192
193#[derive(Clone, Debug, PartialEq, Eq)]
195pub struct LocatedFault<Id, E> {
196 pub instruction: Id,
198 pub location: SourceLocation,
200 pub fault: E,
202}
203
204pub enum DriveOutcome<Id, R, A, Y, I> {
206 Continue(WorkReceipt<Id>),
208 Return(R, WorkReceipt<Id>),
210 Raise(A, WorkReceipt<Id>),
212 Yield(Y, WorkReceipt<Id>),
214 Interrupt(I, WorkReceipt<Id>),
216}
217
218#[derive(Clone, Debug, PartialEq, Eq)]
220pub enum DriveError<Id, E> {
221 PermitMismatch,
223 EmptyFrames,
225 FrameLimit(FrameStackError),
227 Fault(LocatedFault<Id, E>),
229}
230
231pub type DriveResult<Id, R, A, Y, I, E> = Result<DriveOutcome<Id, R, A, Y, I>, DriveError<Id, E>>;
233
234#[derive(Clone, Debug, PartialEq, Eq)]
236pub enum SafepointDriveError<Id, E, S> {
237 RootScan(RootScanError),
239 Safepoint(S),
241 Drive(DriveError<Id, E>),
243}
244
245pub struct Driver<D> {
247 policy: D,
248}
249
250impl<D> Driver<D> {
251 pub fn new(policy: D) -> Self {
253 Self { policy }
254 }
255
256 #[allow(
258 clippy::type_complexity,
259 reason = "the explicit outcome channels are the driver contract"
260 )]
261 pub fn drive<P, M, A, F>(
262 &mut self,
263 description: &MachineDescription<'_, P, M>,
264 permit: &MachinePermit,
265 frames: &mut FrameStack<F>,
266 work: WorkLimit,
267 ) -> DriveResult<P::InstructionId, D::Return, D::Abrupt, D::Yield, D::Interrupt, D::Fault>
268 where
269 P: InstructionPolicy,
270 P::InstructionId: Copy + Eq + Ord,
271 A: AdmissionPolicy<P, M>,
272 F: MachineFrame,
273 D: InstructionDriverPolicy<P, F>,
274 {
275 if !permit.accepts::<P, M, A>(description) {
277 return Err(DriveError::PermitMismatch);
278 }
279 if frames.current().is_none() {
280 return Err(DriveError::EmptyFrames);
281 }
282
283 let mut steps = Vec::with_capacity(work.0);
284 for _ in 0..work.0 {
285 let cursor = frames.current().ok_or(DriveError::EmptyFrames)?.cursor();
286 let located = description.code().instruction(cursor);
287 let id = *located.id();
288 let location = located.location().clone();
289 let outcome = self
290 .policy
291 .step(
292 located.instruction(),
293 frames.current_mut().expect("frame was checked"),
294 )
295 .map_err(|fault| {
296 DriveError::Fault(LocatedFault {
297 instruction: id,
298 location,
299 fault,
300 })
301 })?;
302 match outcome {
303 StepOutcome::Continue(next) => {
304 steps.push((id, StepKind::Continue));
305 frames
306 .current_mut()
307 .expect("current frame remains")
308 .set_cursor(next);
309 }
310 StepOutcome::Call(frame) => {
311 steps.push((id, StepKind::Call));
312 frames.push(frame).map_err(DriveError::FrameLimit)?;
313 }
314 StepOutcome::Return(value) => {
315 steps.push((id, StepKind::Return));
316 frames.pop();
317 if frames.current().is_none() {
318 return Ok(DriveOutcome::Return(value, WorkReceipt { steps }));
319 }
320 }
321 StepOutcome::Raise(value) => {
322 steps.push((id, StepKind::Raise));
323 return Ok(DriveOutcome::Raise(value, WorkReceipt { steps }));
324 }
325 StepOutcome::Yield(value) => {
326 steps.push((id, StepKind::Yield));
327 return Ok(DriveOutcome::Yield(value, WorkReceipt { steps }));
328 }
329 StepOutcome::Interrupt(value) => {
330 steps.push((id, StepKind::Interrupt));
331 return Ok(DriveOutcome::Interrupt(value, WorkReceipt { steps }));
332 }
333 }
334 }
335 Ok(DriveOutcome::Continue(WorkReceipt { steps }))
336 }
337
338 #[allow(
345 clippy::type_complexity,
346 reason = "the explicit outcome channels are the driver contract"
347 )]
348 pub fn drive_with_safepoints<P, M, A, F, S>(
349 &mut self,
350 description: &MachineDescription<'_, P, M>,
351 permit: &MachinePermit,
352 frames: &mut FrameStack<F>,
353 work: WorkLimit,
354 root_budget: WorkLimit,
355 mut observe: impl FnMut(&RootSnapshot) -> Result<(), S>,
356 ) -> Result<
357 DriveOutcome<P::InstructionId, D::Return, D::Abrupt, D::Yield, D::Interrupt>,
358 SafepointDriveError<P::InstructionId, D::Fault, S>,
359 >
360 where
361 P: InstructionPolicy,
362 P::InstructionId: Copy + Eq + Ord,
363 A: AdmissionPolicy<P, M>,
364 F: MachineFrame + ManagedRootSource,
365 D: InstructionDriverPolicy<P, F>,
366 {
367 if !permit.accepts::<P, M, A>(description) {
368 return Err(SafepointDriveError::Drive(DriveError::PermitMismatch));
369 }
370 if frames.current().is_none() {
371 return Err(SafepointDriveError::Drive(DriveError::EmptyFrames));
372 }
373
374 let mut combined = WorkReceipt { steps: Vec::new() };
375 for _ in 0..work.0 {
376 let cursor = frames
377 .current()
378 .ok_or(SafepointDriveError::Drive(DriveError::EmptyFrames))?
379 .cursor();
380 if description.code().instruction(cursor).is_safepoint() {
381 let roots = RootSnapshot::scan(frames, root_budget)
382 .map_err(SafepointDriveError::RootScan)?;
383 observe(&roots).map_err(SafepointDriveError::Safepoint)?;
384 }
385 let outcome = self
386 .drive::<P, M, A, F>(description, permit, frames, WorkLimit(1))
387 .map_err(SafepointDriveError::Drive)?;
388 match outcome {
389 DriveOutcome::Continue(receipt) => combined.append(receipt),
390 DriveOutcome::Return(value, receipt) => {
391 combined.append(receipt);
392 return Ok(DriveOutcome::Return(value, combined));
393 }
394 DriveOutcome::Raise(value, receipt) => {
395 combined.append(receipt);
396 return Ok(DriveOutcome::Raise(value, combined));
397 }
398 DriveOutcome::Yield(value, receipt) => {
399 combined.append(receipt);
400 return Ok(DriveOutcome::Yield(value, combined));
401 }
402 DriveOutcome::Interrupt(value, receipt) => {
403 combined.append(receipt);
404 return Ok(DriveOutcome::Interrupt(value, combined));
405 }
406 }
407 }
408 Ok(DriveOutcome::Continue(combined))
409 }
410
411 #[allow(
417 clippy::type_complexity,
418 reason = "matches the explicit driver channels"
419 )]
420 pub fn drive_protected<P, M, A, F>(
421 &mut self,
422 description: &MachineDescription<'_, P, M>,
423 permit: &MachinePermit,
424 frames: &mut FrameStack<F>,
425 work: WorkLimit,
426 ) -> DriveResult<P::InstructionId, D::Return, D::Abrupt, D::Yield, D::Interrupt, D::Fault>
427 where
428 P: InstructionPolicy,
429 P::InstructionId: Copy + Eq + Ord,
430 A: AdmissionPolicy<P, M>,
431 F: MachineFrame,
432 D: InstructionDriverPolicy<P, F>,
433 {
434 let mut combined = WorkReceipt { steps: Vec::new() };
435 let mut left = work.0;
436 while left > 0 {
437 let cursor = frames.current().ok_or(DriveError::EmptyFrames)?.cursor();
438 let outcome = self.drive::<P, M, A, F>(description, permit, frames, WorkLimit(1))?;
439 match outcome {
440 DriveOutcome::Raise(value, receipt) => {
441 left -= receipt.charged();
442 combined.append(receipt);
443 if let Some(region) = description.code().innermost_protected_region(cursor) {
444 frames
445 .current_mut()
446 .expect("raising frame remains")
447 .set_cursor(region.handler);
448 } else {
449 return Ok(DriveOutcome::Raise(value, combined));
450 }
451 }
452 DriveOutcome::Continue(receipt) => {
453 left -= receipt.charged();
454 combined.append(receipt);
455 if left == 0 {
456 return Ok(DriveOutcome::Continue(combined));
457 }
458 }
459 DriveOutcome::Return(value, receipt) => {
460 combined.append(receipt);
461 return Ok(DriveOutcome::Return(value, combined));
462 }
463 DriveOutcome::Yield(value, receipt) => {
464 combined.append(receipt);
465 return Ok(DriveOutcome::Yield(value, combined));
466 }
467 DriveOutcome::Interrupt(value, receipt) => {
468 combined.append(receipt);
469 return Ok(DriveOutcome::Interrupt(value, combined));
470 }
471 }
472 }
473 Ok(DriveOutcome::Continue(combined))
474 }
475
476 #[allow(
479 clippy::type_complexity,
480 reason = "matches the explicit driver channels"
481 )]
482 pub fn drive_with_cleanup<P, M, A, F>(
483 &mut self,
484 description: &MachineDescription<'_, P, M>,
485 permit: &MachinePermit,
486 frames: &mut FrameStack<F>,
487 work: WorkLimit,
488 cleanups: CleanupStack<MachineUnwind<D::Return, D::Abrupt, D::Interrupt>>,
489 ) -> DriveResult<P::InstructionId, D::Return, D::Abrupt, D::Yield, D::Interrupt, D::Fault>
490 where
491 P: InstructionPolicy,
492 P::InstructionId: Copy + Eq + Ord,
493 A: AdmissionPolicy<P, M>,
494 F: MachineFrame,
495 D: InstructionDriverPolicy<P, F>,
496 D::Return: Clone,
497 D::Abrupt: Clone,
498 D::Interrupt: Clone,
499 {
500 let outcome = match self.drive_protected::<P, M, A, F>(description, permit, frames, work) {
501 Ok(outcome) => outcome,
502 Err(error) => {
503 cleanups.unwind(Unwind::Exception(MachineAbrupt::Fault));
504 return Err(error);
505 }
506 };
507 match &outcome {
508 DriveOutcome::Return(value, _) => {
509 cleanups.unwind(Unwind::Return(value.clone()));
510 }
511 DriveOutcome::Raise(value, _) => {
512 cleanups.unwind(Unwind::Exception(MachineAbrupt::Raise(value.clone())));
513 }
514 DriveOutcome::Interrupt(value, _) => {
515 cleanups.unwind(Unwind::Exception(MachineAbrupt::Interrupt(value.clone())));
516 }
517 DriveOutcome::Continue(_) => {
518 cleanups.unwind(Unwind::Exception(MachineAbrupt::BudgetExhausted));
519 }
520 DriveOutcome::Yield(_, _) => return Ok(outcome),
521 }
522 Ok(outcome)
523 }
524}