zkaluvm/gfa/instr.rs
1// AluVM ISA extension for Galois fields
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Designed in 2024-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
6// Written in 2024-2025 by Dr Maxim Orlovsky <orlovsky@ubideco.org>
7//
8// Copyright (C) 2024-2025 Laboratories for Ubiquitous Deterministic Computing (UBIDECO),
9// Institute for Distributed and Cognitive Systems (InDCS), Switzerland.
10// Copyright (C) 2024-2025 Dr Maxim Orlovsky.
11// All rights under the above copyrights are reserved.
12//
13// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
14// in compliance with the License. You may obtain a copy of the License at
15//
16// http://www.apache.org/licenses/LICENSE-2.0
17//
18// Unless required by applicable law or agreed to in writing, software distributed under the License
19// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
20// or implied. See the License for the specific language governing permissions and limitations under
21// the License.
22
23use aluvm::isa::{CtrlInstr, ReservedInstr};
24use aluvm::SiteId;
25use amplify::num::{u2, u3};
26
27use crate::{fe256, RegE};
28
29/// Instruction set, which includes core AluVM control-flow instructions and GFA256 ISA extension
30/// (see [`FieldInstr`]).
31#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Display, From)]
32#[display(inner)]
33#[non_exhaustive]
34pub enum Instr<Id: SiteId> {
35 /// Control flow instructions.
36 #[from]
37 Ctrl(CtrlInstr<Id>),
38
39 /// Arithmetic instructions for finite fields.
40 #[from]
41 Gfa(FieldInstr),
42
43 /// Reserved instruction for future use in core `ALU` ISAs.
44 #[from]
45 Reserved(ReservedInstr),
46}
47
48/// Arithmetic instructions for finite fields.
49#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Display)]
50#[non_exhaustive]
51pub enum FieldInstr {
52 /// Tests if register contains a value and is not set to `None`.
53 ///
54 /// Sets `CO` register to [`Status::Ok`] if a register contains a value, and to [`Status::Fail`]
55 /// otherwise.
56 ///
57 /// Does not affect the value in the `CK` register.
58 #[display("test {src}")]
59 Test {
60 /** The source register */
61 src: RegE,
62 },
63
64 /// Clears register value by setting it to `None`.
65 ///
66 /// Does not affect values in the `CO` and `CK` registers.
67 #[display("clr {dst}")]
68 Clr {
69 /** The destination register */
70 dst: RegE,
71 },
72
73 /// Puts value into a register, replacing the previous value in it if there was any.
74 ///
75 /// Does not affect values in the `CO` and `CK` registers.
76 #[display("put {dst}, {data}")]
77 PutD {
78 /** The destination register */
79 dst: RegE,
80 /** Finite field element taken from the data segment, used to initialize the register */
81 data: fe256,
82 },
83
84 /// Puts zero (`0`) value into a register, replacing the previous value in it if there was any.
85 ///
86 /// Does not affect values in the `CO` and `CK` registers.
87 #[display("put {dst}, 0")]
88 PutZ {
89 /** The destination register */
90 dst: RegE,
91 },
92
93 /// Puts `val` value, which is a power of 2, into a register, replacing the previous value in
94 /// it if there was any.
95 ///
96 /// Does not affect values in the `CO` and `CK` registers.
97 #[display("put {dst}, {val}")]
98 PutV {
99 /** The destination register */
100 dst: RegE,
101 /** A constant finite field element used to initialize the register */
102 val: ConstVal,
103 },
104
105 /// Test whether a value in a register fits in the provided number of bits.
106 ///
107 /// Sets `CO` register to [`Status::Ok`] if the value fits the given number of bits, and to
108 /// [`Status::Fail`] otherwise.
109 ///
110 /// If `src` is set to `None`, sets both `CO` and `CK` to [`Status::Fail`]; otherwise leaves
111 /// value in the `CK` unchanged.
112 #[display("fits {src}, {bits}")]
113 Fits {
114 /** The source register */
115 src: RegE,
116 /** The maximum bit dimension which the source register value must fit into */
117 bits: Bits,
118 },
119
120 /// Moves (copies) value from `src` to `dst` register, overwriting the previous value in `dst`.
121 /// If `src` has no value (i.e., set to `None`), sets `dst` to `None`.
122 ///
123 /// Leaves the state of the `src` register unaffected.
124 ///
125 /// Does not affect values in the `CO` and `CK` registers.
126 #[display("mov {dst}, {src}")]
127 Mov {
128 /** The destination register */
129 dst: RegE,
130 /** The source register */
131 src: RegE,
132 },
133
134 /// Checks whether `src1` and `src2` registers are equal.
135 ///
136 /// Sets `CO` register to represent equivalence of the registers. If both `src1` and `src2`
137 /// registers contain no value, sets `CK` to a failed state.
138 ///
139 /// Does not affect the value in the `CK` register.
140 #[display("eq {src1}, {src2}")]
141 Eq {
142 /** The first source register */
143 src1: RegE,
144 /** The second source register */
145 src2: RegE,
146 },
147
148 /// Negate value in `src` using finite-field arithmetics, and put result into `dst`.
149 ///
150 /// Does not affect values in the `CO` register.
151 ///
152 /// If `src` is set to `None`, sets `CK` to [`Status::Fail`]; otherwise leaves value in `CK`
153 /// unchanged.
154 #[display("neg {dst}, {src}")]
155 Neg {
156 /** The destination register */
157 dst: RegE,
158 /** The source register */
159 src: RegE,
160 },
161
162 /// Add `src` value to `dst_src` value using finite-field (modulo) arithmetics of the `FQ`
163 /// order, putting the result to `dst_src`.
164 ///
165 /// Does not affect values in the `CO` register.
166 ///
167 /// If either `src` or `dst_src` (or both) is set to `None`, sets `CK` to [`Status::Fail`];
168 /// otherwise leaves value in the `CK` unchanged.
169 #[display("add {dst_src}, {src}")]
170 Add {
171 /** The first source and the destination register */
172 dst_src: RegE,
173 /** The second source register */
174 src: RegE,
175 },
176
177 /// Multiply `src` value to `dst_src` value using finite-field (modulo) arithmetics of the
178 /// `FQ` order, putting the result to `dst_src`.
179 ///
180 /// Does not affect values in the `CO` register.
181 ///
182 /// If either `src` or `dst_src` (or both) is set to `None`, sets `CK` to [`Status::Fail`];
183 /// otherwise leaves value in the `CK` unchanged.
184 #[display("mul {dst_src}, {src}")]
185 Mul {
186 /** The first source and the destination register */
187 dst_src: RegE,
188 /** The second source register */
189 src: RegE,
190 },
191}
192
193/// A predefined constant field element for a register initialization.
194///
195/// These constants are used to keep the space and complexity metric of the code low, since reading
196/// a field element from the data segment will take 16 bytes in the code segment; while initializing
197/// with a common constant will take just 2 bits.
198#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Display)]
199#[repr(u8)]
200pub enum ConstVal {
201 /// Zero field element.
202 #[display("1")]
203 Val1 = 0,
204
205 /// Field element equal to the [`u64::MAX`].
206 #[display("ffff_ffff_ffff_ffff#h")]
207 ValU64Max = 1,
208
209 /// Field element equal to the [`u128::MAX`].
210 #[display("ffff_ffff_ffff_ffff_ffff_ffff_ffff_ffff#h")]
211 ValU128Max = 2,
212
213 /// Field element equal to the finite field order minus one. The finite field order value is
214 /// taken from the constant `FQ` register.
215 #[display("-1#fe")]
216 ValFeMAX = 3,
217}
218
219impl From<u2> for ConstVal {
220 fn from(val: u2) -> Self {
221 match val {
222 x if x == ConstVal::Val1.to_u2() => ConstVal::Val1,
223 x if x == ConstVal::ValU64Max.to_u2() => ConstVal::ValU64Max,
224 x if x == ConstVal::ValU128Max.to_u2() => ConstVal::ValU128Max,
225 x if x == ConstVal::ValFeMAX.to_u2() => ConstVal::ValFeMAX,
226 _ => unreachable!(),
227 }
228 }
229}
230
231impl ConstVal {
232 /// Get a 2-bit representation of the constant value.
233 #[inline]
234 pub const fn to_u2(self) -> u2 { u2::with(self as u8) }
235
236 /// Get a finite field element corresponding to the constant.
237 ///
238 /// Returns `None` for the [`ConstVal::ValFeMAX`].
239 pub fn to_fe256(self) -> Option<fe256> {
240 let val = match self {
241 ConstVal::Val1 => 1u128,
242 ConstVal::ValU64Max => u64::MAX as u128,
243 ConstVal::ValU128Max => u128::MAX,
244 ConstVal::ValFeMAX => return None,
245 };
246 Some(fe256::from(val))
247 }
248}
249
250/// Maximum bit dimension which a register value should fit (used in [`FieldInstr::Fits`]
251/// instruction).
252#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Display)]
253#[repr(u8)]
254pub enum Bits {
255 /// 8 bits (a byte).
256 #[display("8.bits")]
257 Bits8,
258
259 /// 16 bits (two bytes).
260 #[display("16.bits")]
261 Bits16,
262
263 /// 24 bits (three bytes).
264 #[display("24.bits")]
265 Bits24,
266
267 /// 32 bits (four bytes).
268 #[display("32.bits")]
269 Bits32,
270
271 /// 48 bits (six bytes).
272 #[display("48.bits")]
273 Bits48,
274
275 /// 64 bits (8 bytes).
276 #[display("64.bits")]
277 Bits64,
278
279 /// 96 bits (12 bytes).
280 #[display("96.bits")]
281 Bits96,
282
283 /// 128 bits (16 bytes).
284 #[display("128.bits")]
285 Bits128,
286}
287
288impl From<u3> for Bits {
289 fn from(val: u3) -> Self {
290 match val {
291 x if x == Bits::Bits8.to_u3() => Bits::Bits8,
292 x if x == Bits::Bits16.to_u3() => Bits::Bits16,
293 x if x == Bits::Bits24.to_u3() => Bits::Bits24,
294 x if x == Bits::Bits32.to_u3() => Bits::Bits32,
295 x if x == Bits::Bits48.to_u3() => Bits::Bits48,
296 x if x == Bits::Bits64.to_u3() => Bits::Bits64,
297 x if x == Bits::Bits96.to_u3() => Bits::Bits96,
298 x if x == Bits::Bits128.to_u3() => Bits::Bits96,
299 _ => unreachable!(),
300 }
301 }
302}
303
304impl Bits {
305 /// Get a 3-bit representation of the bit dimension variant.
306 #[inline]
307 pub const fn to_u3(self) -> u3 { u3::with(self as u8) }
308
309 /// Construct a dimension variant a bit out of bit length.
310 ///
311 /// # Panics
312 ///
313 /// If there is no enum variant matching the provided bit length.
314 pub fn from_bit_len(len: usize) -> Self {
315 match len {
316 8 => Bits::Bits8,
317 16 => Bits::Bits16,
318 24 => Bits::Bits24,
319 32 => Bits::Bits32,
320 48 => Bits::Bits48,
321 64 => Bits::Bits64,
322 96 => Bits::Bits96,
323 128 => Bits::Bits128,
324 invalid => panic!("unsupported bit length {invalid}"),
325 }
326 }
327
328 /// Returns a bit length corresponding to the enum variant.
329 pub const fn bit_len(self) -> usize {
330 match self {
331 Bits::Bits8 => 8,
332 Bits::Bits16 => 16,
333 Bits::Bits24 => 24,
334 Bits::Bits32 => 32,
335 Bits::Bits48 => 48,
336 Bits::Bits64 => 64,
337 Bits::Bits96 => 96,
338 Bits::Bits128 => 128,
339 }
340 }
341}