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
    }
}