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
// 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-2022 by
//     Dr. Maxim Orlovsky <orlovsky@lnp-bp.org>
// 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>.

use alloc::borrow::ToOwned;
use alloc::collections::BTreeMap;
use alloc::string::String;
use core::marker::PhantomData;

use super::constants::LIBS_MAX_TOTAL;
use super::*;
use crate::isa::InstructionSet;

/// Errors returned by [`Program::add_lib`] method
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[cfg_attr(feature = "std", derive(Error))]
#[display(doc_comments)]
pub enum LibError {
    /// 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,
}

/// An AluVM program executable by a virtual machine.
///
/// # Generics
///
/// `RUNTIME_MAX_TOTAL_LIBS`: Maximum total number of libraries supported by a runtime, if it is
/// less than [`LIBS_MAX_TOTAL`]. If the value set is greater than [`LIBS_MAX_TOTAL`] the
/// value is ignored and [`LIBS_MAX_TOTAL`] constant is used instead.
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
#[cfg_attr(feature = "strict_encoding", derive(StrictEncode, StrictDecode))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct Program<Isa, const RUNTIME_MAX_TOTAL_LIBS: u16 = LIBS_MAX_TOTAL>
where
    Isa: InstructionSet,
{
    /// Libraries known to the runtime, identified by their hashes
    libs: BTreeMap<LibId, Lib>,

    /// Entrypoint for the main function
    entrypoint: LibSite,

    #[cfg_attr(feature = "strict_encoding", strict_encoding(skip))]
    #[cfg_attr(feature = "serde", serde(skip))]
    phantom: PhantomData<Isa>,
}

impl<Isa, const RUNTIME_MAX_TOTAL_LIBS: u16> Program<Isa, RUNTIME_MAX_TOTAL_LIBS>
where
    Isa: InstructionSet,
{
    const RUNTIME_MAX_TOTAL_LIBS: u16 = RUNTIME_MAX_TOTAL_LIBS;

    fn empty_unchecked() -> Self {
        Program {
            libs: BTreeMap::new(),
            entrypoint: LibSite::with(0, zero!()),
            phantom: default!(),
        }
    }

    /// Constructs new virtual machine runtime using provided single library. Entry point is set
    /// to zero offset by default.
    pub fn new(lib: Lib) -> Self {
        let mut runtime = Self::empty_unchecked();
        let id = lib.id();
        runtime.add_lib(lib).expect("adding single library to lib segment overflows");
        runtime.set_entrypoint(LibSite::with(0, id));
        runtime
    }

    /// Constructs new virtual machine runtime from a set of libraries with a given entry point.
    pub fn with(
        libs: impl IntoIterator<Item = Lib>,
        entrypoint: LibSite,
    ) -> Result<Self, LibError> {
        let mut runtime = Self::empty_unchecked();
        for lib in libs {
            runtime.add_lib(lib)?;
        }
        runtime.set_entrypoint(entrypoint);
        Ok(runtime)
    }

    /// Returns reference to a specific library, if it is part of the current program.
    pub fn lib(&self, id: LibId) -> Option<&Lib> { self.libs.get(&id) }

    /// Adds Alu bytecode library to the virtual machine runtime.
    ///
    /// # Errors
    ///
    /// Checks requirement that the total number of libraries must not exceed [`LIBS_MAX_TOTAL`]
    /// and `RUNTIME_MAX_TOTAL_LIBS` - or returns [`LibError::TooManyLibs`] otherwise.
    ///
    /// Checks that the ISA used by the VM supports ISA extensions specified by the library and
    /// returns [`LibError::IsaNotSupported`] otherwise.
    ///
    /// # Returns
    ///
    /// `true` if the library was already known and `false` otherwise.
    #[inline]
    pub fn add_lib(&mut self, lib: Lib) -> Result<bool, LibError> {
        if self.libs_count() >= LIBS_MAX_TOTAL.min(Self::RUNTIME_MAX_TOTAL_LIBS) {
            return Err(LibError::TooManyLibs);
        }
        for isa in &lib.isae {
            if !Isa::is_supported(isa) {
                return Err(LibError::IsaNotSupported(isa.to_owned()));
            }
        }
        Ok(self.libs.insert(lib.id(), lib).is_none())
    }

    /// Returns number of libraries used by the program.
    pub fn libs_count(&self) -> u16 { self.libs.len() as u16 }

    /// Returns program entry point.
    pub fn entrypoint(&self) -> LibSite { self.entrypoint }

    // TODO: Return error if the library is not known
    /// Sets new entry point value (used when calling [`crate::Vm::run`])
    pub fn set_entrypoint(&mut self, entrypoint: LibSite) { self.entrypoint = entrypoint; }
}