pd-vm 0.22.2

RustScript bytecode compiler and VM
Documentation
use super::*;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FuelCheckpoint {
    remaining: Option<u64>,
    check_interval: u32,
    ops_until_check: u32,
}

impl FuelCheckpoint {
    pub fn fuel(&self) -> Option<u64> {
        self.remaining
    }

    pub fn check_interval(&self) -> u32 {
        self.check_interval
    }
}

impl Vm {
    pub(super) fn pending_fuel_debt(&self) -> u64 {
        if !self.fuel_metering_enabled() {
            return 0;
        }
        let executed_since_last_check = self
            .fuel_check_interval
            .saturating_sub(self.fuel_ops_until_check);
        u64::from(executed_since_last_check)
    }

    #[inline(always)]
    pub(in crate::vm) fn charge_fuel(&mut self, amount: u64) -> VmResult<()> {
        if amount == 0 || !self.fuel_metering_enabled() {
            return Ok(());
        }

        let remaining = self.fuel_remaining;
        if remaining < amount {
            return Err(VmError::OutOfFuel {
                needed: amount,
                remaining,
            });
        }
        self.fuel_remaining = remaining - amount;
        Ok(())
    }

    #[inline(always)]
    pub(in crate::vm) fn charge_fuel_tick(&mut self) -> VmResult<()> {
        if !self.fuel_metering_enabled() {
            return Ok(());
        }
        if self.fuel_ops_until_check > 1 {
            self.fuel_ops_until_check -= 1;
            return Ok(());
        }

        let amount = u64::from(self.fuel_check_interval);
        self.charge_fuel(amount)?;
        self.fuel_ops_until_check = self.fuel_check_interval;
        Ok(())
    }

    pub(super) fn clear_fuel_internal(&mut self) {
        if self.fuel_metering_enabled() {
            self.interrupt_mode = InterruptMode::None;
        }
        self.fuel_remaining = 0;
        self.reset_interrupt_countdown();
    }

    pub fn set_fuel(&mut self, fuel: u64) {
        self.clear_epoch_deadline_internal();
        self.interrupt_mode = InterruptMode::Fuel;
        self.fuel_remaining = fuel;
        self.reset_interrupt_countdown();
    }

    pub fn clear_fuel(&mut self) {
        self.clear_fuel_internal();
    }

    pub fn set_fuel_check_interval(&mut self, interval: u32) -> VmResult<()> {
        if interval == 0 {
            return Err(VmError::InvalidFuelCheckInterval(interval));
        }
        if self.epoch_interruption_enabled() {
            return Err(self.interruption_mode_conflict(InterruptMode::Fuel));
        }
        self.fuel_check_interval = interval;
        self.reset_interrupt_countdown();
        Ok(())
    }

    pub fn fuel_check_interval(&self) -> u32 {
        self.fuel_check_interval
    }

    pub fn get_fuel(&self) -> Option<u64> {
        self.fuel_metering_enabled()
            .then_some(self.fuel_remaining.saturating_sub(self.pending_fuel_debt()))
    }

    pub fn add_fuel(&mut self, fuel: u64) -> VmResult<()> {
        if fuel == 0 {
            return Ok(());
        }
        if self.epoch_interruption_enabled() {
            return Err(self.interruption_mode_conflict(InterruptMode::Fuel));
        }
        self.fuel_remaining = if self.fuel_metering_enabled() {
            self.fuel_remaining
                .checked_add(fuel)
                .ok_or(VmError::FuelOverflow)?
        } else {
            self.interrupt_mode = InterruptMode::Fuel;
            self.reset_interrupt_countdown();
            fuel
        };
        Ok(())
    }

    pub fn recharge_fuel(&mut self, fuel: u64) -> VmResult<()> {
        self.add_fuel(fuel)
    }

    pub fn consume_fuel(&mut self, fuel: u64) -> VmResult<()> {
        if self.epoch_interruption_enabled() {
            return Err(self.interruption_mode_conflict(InterruptMode::Fuel));
        }
        self.charge_fuel(fuel)
    }

    pub fn consume_fuel_tick(&mut self) -> VmResult<()> {
        if self.epoch_interruption_enabled() {
            return Err(self.interruption_mode_conflict(InterruptMode::Fuel));
        }
        self.charge_fuel_tick()
    }

    pub fn fuel_checkpoint(&self) -> FuelCheckpoint {
        FuelCheckpoint {
            remaining: self.fuel_metering_enabled().then_some(self.fuel_remaining),
            check_interval: self.fuel_check_interval(),
            ops_until_check: self.fuel_ops_until_check,
        }
    }

    pub fn checkpoint(&self) -> FuelCheckpoint {
        self.fuel_checkpoint()
    }

    pub fn restore_fuel(&mut self, checkpoint: FuelCheckpoint) {
        self.clear_epoch_deadline_internal();
        self.interrupt_mode = if checkpoint.remaining.is_some() {
            InterruptMode::Fuel
        } else {
            InterruptMode::None
        };
        self.fuel_remaining = checkpoint.remaining.unwrap_or(0);
        self.fuel_check_interval = checkpoint.check_interval.max(1);
        self.fuel_ops_until_check = checkpoint
            .ops_until_check
            .clamp(1, self.fuel_check_interval);
    }

    pub fn restore_checkpoint(&mut self, checkpoint: FuelCheckpoint) {
        self.restore_fuel(checkpoint);
    }
}