Skip to main content

vm/vm/
epoch.rs

1use std::sync::Arc;
2use std::sync::atomic::{AtomicU64, Ordering};
3
4use super::*;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct EpochCheckpoint {
8    deadline: Option<u64>,
9    deadline_delta: u64,
10    rearm_pending: bool,
11    check_interval: u32,
12    ops_until_check: u32,
13}
14
15impl EpochCheckpoint {
16    pub fn deadline(&self) -> Option<u64> {
17        self.deadline
18    }
19
20    pub fn check_interval(&self) -> u32 {
21        self.check_interval
22    }
23}
24
25#[derive(Clone, Debug, Default)]
26pub struct EpochHandle {
27    current: Arc<AtomicU64>,
28}
29
30impl EpochHandle {
31    pub fn current(&self) -> u64 {
32        self.current.load(Ordering::Acquire)
33    }
34
35    pub fn increment(&self) -> u64 {
36        self.increment_by(1)
37    }
38
39    pub fn increment_by(&self, delta: u64) -> u64 {
40        if delta == 0 {
41            return self.current();
42        }
43        let previous = self
44            .current
45            .fetch_update(Ordering::AcqRel, Ordering::Acquire, |current| {
46                Some(current.saturating_add(delta))
47            })
48            .unwrap_or_else(|current| current);
49        previous.saturating_add(delta)
50    }
51
52    pub(super) fn as_ptr(&self) -> *const AtomicU64 {
53        Arc::as_ptr(&self.current)
54    }
55}
56
57impl Vm {
58    #[inline(always)]
59    pub(in crate::vm) fn charge_epoch_tick(&mut self) -> VmResult<()> {
60        if !self.epoch_interruption_enabled() {
61            return Ok(());
62        }
63        if self.fuel_ops_until_check > 1 {
64            self.fuel_ops_until_check -= 1;
65            return Ok(());
66        }
67
68        let current = self.current_epoch();
69        if current >= self.epoch_deadline {
70            return Err(VmError::EpochDeadlineReached {
71                current,
72                deadline: self.epoch_deadline,
73            });
74        }
75        self.fuel_ops_until_check = self.fuel_check_interval;
76        Ok(())
77    }
78
79    #[inline(always)]
80    pub(super) fn mark_interrupt_yield(&mut self, reason: VmYieldReason) {
81        self.last_yield_reason = Some(reason);
82        if matches!(reason, VmYieldReason::Epoch) {
83            self.epoch_rearm_pending = true;
84        }
85    }
86
87    #[inline(always)]
88    pub(super) fn rearm_epoch_after_yield_if_needed(&mut self) {
89        if !self.epoch_rearm_pending {
90            return;
91        }
92        if !self.epoch_interruption_enabled() {
93            self.epoch_rearm_pending = false;
94            return;
95        }
96        self.epoch_deadline = self
97            .current_epoch()
98            .saturating_add(self.epoch_deadline_delta);
99        self.epoch_rearm_pending = false;
100        self.reset_interrupt_countdown();
101    }
102
103    pub(super) fn clear_epoch_deadline_internal(&mut self) {
104        if self.epoch_interruption_enabled() {
105            self.interrupt_mode = InterruptMode::None;
106        }
107        self.epoch_deadline = 0;
108        self.epoch_deadline_delta = 0;
109        self.epoch_rearm_pending = false;
110        self.reset_interrupt_countdown();
111    }
112
113    pub fn consume_epoch_tick(&mut self) -> VmResult<()> {
114        if self.fuel_metering_enabled() {
115            return Err(self.interruption_mode_conflict(InterruptMode::Epoch));
116        }
117        self.charge_epoch_tick()
118    }
119
120    pub fn epoch_handle(&self) -> EpochHandle {
121        self.epoch_handle.clone()
122    }
123
124    pub fn current_epoch(&self) -> u64 {
125        self.epoch_handle.current()
126    }
127
128    pub fn increment_epoch(&self) -> u64 {
129        self.epoch_handle.increment()
130    }
131
132    pub fn increment_epoch_by(&self, delta: u64) -> u64 {
133        self.epoch_handle.increment_by(delta)
134    }
135
136    pub fn set_epoch_deadline(&mut self, ticks_beyond_current: u64) -> VmResult<()> {
137        if self.fuel_metering_enabled() {
138            return Err(self.interruption_mode_conflict(InterruptMode::Epoch));
139        }
140        self.interrupt_mode = InterruptMode::Epoch;
141        self.epoch_deadline = self.current_epoch().saturating_add(ticks_beyond_current);
142        self.epoch_deadline_delta = ticks_beyond_current;
143        self.epoch_rearm_pending = false;
144        self.reset_interrupt_countdown();
145        Ok(())
146    }
147
148    pub fn clear_epoch_deadline(&mut self) {
149        self.clear_epoch_deadline_internal();
150    }
151
152    pub fn epoch_deadline(&self) -> Option<u64> {
153        self.epoch_interruption_enabled()
154            .then_some(self.epoch_deadline)
155    }
156
157    pub fn epoch_deadline_delta(&self) -> Option<u64> {
158        self.epoch_interruption_enabled()
159            .then_some(self.epoch_deadline_delta)
160    }
161
162    pub fn set_epoch_check_interval(&mut self, interval: u32) -> VmResult<()> {
163        if interval == 0 {
164            return Err(VmError::InvalidEpochCheckInterval(interval));
165        }
166        if self.fuel_metering_enabled() {
167            return Err(self.interruption_mode_conflict(InterruptMode::Epoch));
168        }
169        self.fuel_check_interval = interval;
170        self.reset_interrupt_countdown();
171        Ok(())
172    }
173
174    pub fn epoch_check_interval(&self) -> u32 {
175        self.fuel_check_interval()
176    }
177
178    pub fn epoch_checkpoint(&self) -> EpochCheckpoint {
179        EpochCheckpoint {
180            deadline: self
181                .epoch_interruption_enabled()
182                .then_some(self.epoch_deadline),
183            deadline_delta: self.epoch_deadline_delta,
184            rearm_pending: self.epoch_rearm_pending,
185            check_interval: self.epoch_check_interval(),
186            ops_until_check: self.fuel_ops_until_check,
187        }
188    }
189
190    pub fn restore_epoch(&mut self, checkpoint: EpochCheckpoint) {
191        self.clear_fuel_internal();
192        self.interrupt_mode = if checkpoint.deadline.is_some() {
193            InterruptMode::Epoch
194        } else {
195            InterruptMode::None
196        };
197        self.epoch_deadline = checkpoint.deadline.unwrap_or(0);
198        self.epoch_deadline_delta = checkpoint.deadline_delta;
199        self.epoch_rearm_pending = checkpoint.rearm_pending;
200        self.fuel_check_interval = checkpoint.check_interval.max(1);
201        self.fuel_ops_until_check = checkpoint
202            .ops_until_check
203            .clamp(1, self.fuel_check_interval);
204    }
205
206    pub fn last_yield_reason(&self) -> Option<VmYieldReason> {
207        self.last_yield_reason
208    }
209}