aluvm/isa/bytecode.rs
1// Reference rust implementation of AluVM (arithmetic logic unit virtual machine).
2// To find more on AluVM please check <https://aluvm.org>
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Designed in 2021-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
7// Written in 2021-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
8//
9// Copyright (C) 2021-2024 LNP/BP Standards Association, Switzerland.
10// Copyright (C) 2024-2025 Laboratories for Ubiquitous Deterministic Computing (UBIDECO),
11// Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
12// Copyright (C) 2021-2025 Dr Maxim Orlovsky.
13// All rights under the above copyrights are reserved.
14//
15// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
16// in compliance with the License. You may obtain a copy of the License at
17//
18// http://www.apache.org/licenses/LICENSE-2.0
19//
20// Unless required by applicable law or agreed to in writing, software distributed under the License
21// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
22// or implied. See the License for the specific language governing permissions and limitations under
23// the License.
24
25use core::fmt::Debug;
26use core::ops::RangeInclusive;
27
28use amplify::confinement::SmallBlob;
29use amplify::num::{u1, u2, u3, u4, u5, u6, u7};
30
31use crate::core::SiteId;
32
33/// Non-failing byte encoding for the instruction set.
34///
35/// We can't use `io` since (1) we are no_std, (2) it operates data with unlimited length (while we
36/// are bound by u16), (3) it provides too many fails in situations when we can't fail because of
37/// `u16`-bounding and exclusive in-memory encoding handling.
38pub trait Bytecode<Id: SiteId> {
39 /// Returns the range of instruction bytecodes covered by a set of operations.
40 fn op_range() -> RangeInclusive<u8>;
41
42 /// Returns byte representing instruction code (without its arguments).
43 fn opcode_byte(&self) -> u8;
44
45 /// Returns the number of bytes used by the instruction and its arguments when serialized into
46 /// the code segment.
47 fn code_byte_len(&self) -> u16;
48
49 /// If the instruction calls or references any external program, returns a reference to it.
50 fn external_ref(&self) -> Option<Id>;
51
52 /// Write an instruction as bytecode.
53 fn encode_instr<W>(&self, writer: &mut W) -> Result<(), W::Error>
54 where W: BytecodeWrite<Id> {
55 writer.write_byte(self.opcode_byte())?;
56 self.encode_operands(writer)?;
57 writer.check_aligned();
58 Ok(())
59 }
60
61 /// Writes an instruction operands as bytecode, omitting opcode byte.
62 fn encode_operands<W>(&self, writer: &mut W) -> Result<(), W::Error>
63 where W: BytecodeWrite<Id>;
64
65 /// Reads an instruction from bytecode.
66 fn decode_instr<R>(reader: &mut R) -> Result<Self, CodeEofError>
67 where
68 Self: Sized,
69 R: BytecodeRead<Id>,
70 {
71 let opcode = reader.read_byte()?;
72 let instr = Self::decode_operands(reader, opcode)?;
73 reader.check_aligned();
74 Ok(instr)
75 }
76
77 /// Reads an instruction operands from bytecode, provided the opcode byte.
78 fn decode_operands<R>(reader: &mut R, opcode: u8) -> Result<Self, CodeEofError>
79 where
80 Self: Sized,
81 R: BytecodeRead<Id>;
82}
83
84/// Error indicating that an end-of-code segment boundary is reached during read or write operation.
85#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error)]
86#[display("attempt to read or write outside of a code segment (i.e., at position > 0xFFFF)")]
87pub struct CodeEofError;
88
89/// Reader from a bytecode for instruction deserialization.
90pub trait BytecodeRead<Id: SiteId> {
91 /// Return the current byte offset of the cursor. Does not account for bits.
92 /// If the position is exactly at EOF, returns `None`.
93 fn pos(&self) -> u16;
94 /// Set the current cursor byte offset to the provided value if it is less than the underlying
95 /// buffer length.
96 ///
97 /// # Returns
98 ///
99 /// Previous position.
100 fn seek(&mut self, byte_pos: u16) -> Result<u16, CodeEofError>;
101 /// Return whether end of the bytecode is reached.
102 fn is_eof(&self) -> bool;
103 /// Peek a single byte without moving cursor.
104 fn peek_byte(&self) -> Result<u8, CodeEofError>;
105
106 /// Read single bit as a bool value.
107 fn read_bool(&mut self) -> Result<bool, CodeEofError> { Ok(self.read_1bit()? == u1::ONE) }
108 /// Read single bit.
109 fn read_1bit(&mut self) -> Result<u1, CodeEofError>;
110 /// Read two bits.
111 fn read_2bits(&mut self) -> Result<u2, CodeEofError>;
112 /// Read three bits.
113 fn read_3bits(&mut self) -> Result<u3, CodeEofError>;
114 /// Read four bits.
115 fn read_4bits(&mut self) -> Result<u4, CodeEofError>;
116 /// Read five bits.
117 fn read_5bits(&mut self) -> Result<u5, CodeEofError>;
118 /// Read six bits.
119 fn read_6bits(&mut self) -> Result<u6, CodeEofError>;
120 /// Read seven bits.
121 fn read_7bits(&mut self) -> Result<u7, CodeEofError>;
122
123 /// Read byte.
124 fn read_byte(&mut self) -> Result<u8, CodeEofError>;
125 /// Read word.
126 fn read_word(&mut self) -> Result<u16, CodeEofError>;
127
128 /// Read the fixed number of bytes and convert it into a result type.
129 ///
130 /// # Returns
131 ///
132 /// Resulting data type and a flag for `CK` registry indicating whether it was possible to read
133 /// all the data.
134 fn read_fixed<N, const LEN: usize>(
135 &mut self,
136 f: impl FnOnce([u8; LEN]) -> N,
137 ) -> Result<N, CodeEofError>;
138
139 /// Read variable-length byte string.
140 ///
141 /// # Returns
142 ///
143 /// Resulting data type and a flag for `CK` registry indicating whether it was possible to read
144 /// all the data.
145 fn read_bytes(&mut self) -> Result<(SmallBlob, bool), CodeEofError>;
146
147 /// Read external reference id.
148 fn read_ref(&mut self) -> Result<Id, CodeEofError>
149 where Id: Sized;
150
151 /// Check if the current cursor position is aligned to the next byte.
152 ///
153 /// # Panics
154 ///
155 /// If the position is not aligned, panics.
156 fn check_aligned(&self);
157}
158
159/// Writer converting instructions into a bytecode.
160pub trait BytecodeWrite<Id: SiteId> {
161 /// Error type returned during writing procedures.
162 type Error: Debug;
163
164 /// Write a single bit from a bool value.
165 fn write_bool(&mut self, data: bool) -> Result<(), Self::Error> {
166 self.write_1bit(if data { u1::ONE } else { u1::ZERO })
167 }
168
169 /// Write a single bit.
170 fn write_1bit(&mut self, data: u1) -> Result<(), Self::Error>;
171 /// Write two bits.
172 fn write_2bits(&mut self, data: u2) -> Result<(), Self::Error>;
173 /// Write three bits.
174 fn write_3bits(&mut self, data: u3) -> Result<(), Self::Error>;
175 /// Write four bits.
176 fn write_4bits(&mut self, data: u4) -> Result<(), Self::Error>;
177 /// Write five bits.
178 fn write_5bits(&mut self, data: u5) -> Result<(), Self::Error>;
179 /// Write six bits.
180 fn write_6bits(&mut self, data: u6) -> Result<(), Self::Error>;
181 /// Write seven bits.
182 fn write_7bits(&mut self, data: u7) -> Result<(), Self::Error>;
183
184 /// Write byte.
185 fn write_byte(&mut self, data: u8) -> Result<(), Self::Error>;
186 /// Write word.
187 fn write_word(&mut self, data: u16) -> Result<(), Self::Error>;
188
189 /// Write data representable as a fixed-length byte array.
190 fn write_fixed<const LEN: usize>(&mut self, data: [u8; LEN]) -> Result<(), Self::Error>;
191
192 /// Write variable-length byte string.
193 fn write_bytes(&mut self, data: &[u8]) -> Result<(), Self::Error>;
194
195 /// Write external reference id.
196 fn write_ref(&mut self, id: Id) -> Result<(), Self::Error>;
197
198 /// Check if the current cursor position is aligned to the next byte.
199 ///
200 /// # Panics
201 ///
202 /// If the position is not aligned, panics.
203 fn check_aligned(&self);
204}