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}