1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
// Reference rust implementation of AluVM (arithmetic logic unit virtual machine).
// To find more on AluVM please check <https://github.com/internet2-org/aluvm-spec>
//
// Designed & written in 2021 by
//     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
// This work is donated to LNP/BP Standards Association by Pandora Core AG
//
// This software is licensed under the terms of MIT License.
// You should have received a copy of the MIT License along with this software.
// If not, see <https://opensource.org/licenses/MIT>.
//! Alu virtual machine
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::string::String;
use core::marker::PhantomData;
use crate::isa::{Instr, InstructionSet, ReservedOp};
use crate::libs::constants::LIBS_MAX_TOTAL;
use crate::libs::{Lib, LibId, LibSite};
use crate::reg::CoreRegs;
/// Errors returned by [`Vm::add_lib`] method
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[cfg_attr(feature = "std", derive(Error))]
#[display(doc_comments)]
pub enum Error {
    /// ISA id {0} is not supported by the selected instruction set
    IsaNotSupported(String),
    /// Attempt to add library when maximum possible number of libraries is already present in the
    /// VM
    TooManyLibs,
}
/// Alu virtual machine providing single-core execution environment
#[derive(Getters, Debug, Default)]
pub struct Vm<Isa = Instr<ReservedOp>>
where
    Isa: InstructionSet,
{
    /// Libraries known to the runtime, identified by their hashes
    libs: BTreeMap<LibId, Lib>,
    /// Entrypoint for the main function
    entrypoint: LibSite,
    /// A set of registers
    registers: Box<CoreRegs>,
    phantom: PhantomData<Isa>,
}
impl<Isa> Vm<Isa>
where
    Isa: InstructionSet,
{
    /// Constructs new virtual machine with no code in it.
    ///
    /// Calling [`Vm::main`] on it will result in machine termination with `st0` set to `false`.
    pub fn new() -> Vm<Isa> {
        Vm {
            libs: Default::default(),
            entrypoint: Default::default(),
            registers: Default::default(),
            phantom: Default::default(),
        }
    }
    /// Constructs new virtual machine using the provided library.
    pub fn with(lib: Lib) -> Vm<Isa> {
        let mut runtime = Vm::new();
        runtime.entrypoint = LibSite::with(0, lib.id());
        runtime.add_lib(lib).expect("adding single library to lib segment overflows");
        runtime
    }
    /// Adds Alu bytecode library to the virtual machine.
    ///
    /// # Errors
    ///
    /// Checks requirement that the total number of libraries must not exceed [`LIBS_MAX_TOTAL`] and
    /// returns [`Error::TooManyLibs`] otherwise.
    ///
    /// Checks that the ISA used by the VM supports ISA extensions specified by the library and
    /// returns [`Error::IsaNotSupported`] otherwise.
    ///
    /// # Returns
    ///
    /// `true` if the library was already known and `false` otherwise.
    #[inline]
    pub fn add_lib(&mut self, lib: Lib) -> Result<bool, Error> {
        if self.libs.len() >= LIBS_MAX_TOTAL {
            return Err(Error::TooManyLibs);
        }
        for isa in &lib.isae {
            if !Isa::is_supported(isa) {
                return Err(Error::IsaNotSupported(isa.to_owned()));
            }
        }
        Ok(self.libs.insert(lib.id(), lib).is_none())
    }
    /// Sets new entry point value (used when calling [`Vm::main`])
    pub fn set_entrypoint(&mut self, entrypoint: LibSite) { self.entrypoint = entrypoint; }
    /// Executes the program starting from the provided entry point (set with [`Vm::set_entrypoint`]
    /// or initialized to 0 offset of the first used library if [`Vm::new`], [`Vm::default`] or
    /// [`Vm::with`] were used).
    ///
    /// # Returns
    ///
    /// Value of the `st0` register at the end of the program execution.
    pub fn main(&mut self) -> bool { self.call(self.entrypoint) }
    /// Executes the program starting from the provided entry point.
    ///
    /// # Returns
    ///
    /// Value of the `st0` register at the end of the program execution.
    pub fn call(&mut self, method: LibSite) -> bool {
        let mut call = Some(method);
        while let Some(ref mut site) = call {
            if let Some(lib) = self.libs.get(&site.lib) {
                call = lib.run::<Isa>(site.pos, &mut self.registers);
            } else if let Some(pos) = site.pos.checked_add(1) {
                site.pos = pos;
            } else {
                call = None;
            };
        }
        self.registers.st0
    }
}