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}