zkaluvm/gfa/
bytecode.rs

1// AluVM extensions for zero knowledge, STARKs and SNARKs"
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 core::ops::RangeInclusive;
24
25use aluvm::isa::{Bytecode, BytecodeRead, BytecodeWrite, CodeEofError, CtrlInstr, ReservedInstr};
26use aluvm::SiteId;
27use amplify::num::{u2, u256, u3, u4};
28
29use super::{Bits, ConstVal, FieldInstr, Instr};
30use crate::{fe256, RegE};
31
32impl FieldInstr {
33    const START: u8 = 64;
34    const END: u8 = Self::START + Self::MUL;
35    const SET: u8 = 0;
36    const MOV: u8 = 1;
37    const EQ: u8 = 2;
38    const NEG: u8 = 3;
39    const ADD: u8 = 4;
40    const MUL: u8 = 5;
41}
42
43const SUB_TEST: u8 = 0b_0000;
44const SUB_CLR: u8 = 0b_0001;
45const SUB_PUTD: u8 = 0b_0010;
46const SUB_PUTZ: u8 = 0b_0011;
47const MASK_PUTV: u8 = 0b_1100;
48const TEST_PUTV: u8 = 0b_0100;
49const MASK_FITS: u8 = 0b_1000;
50const TEST_FITS: u8 = 0b_1000;
51
52impl<Id: SiteId> Bytecode<Id> for FieldInstr {
53    fn op_range() -> RangeInclusive<u8> { Self::START..=Self::END }
54
55    fn opcode_byte(&self) -> u8 {
56        Self::START
57            + match *self {
58                FieldInstr::Test { .. }
59                | FieldInstr::Clr { .. }
60                | FieldInstr::PutD { .. }
61                | FieldInstr::PutZ { .. }
62                | FieldInstr::PutV { .. }
63                | FieldInstr::Fits { .. } => Self::SET,
64                FieldInstr::Mov { .. } => Self::MOV,
65                FieldInstr::Eq { .. } => Self::EQ,
66                FieldInstr::Neg { .. } => Self::NEG,
67                FieldInstr::Add { .. } => Self::ADD,
68                FieldInstr::Mul { .. } => Self::MUL,
69            }
70    }
71
72    fn code_byte_len(&self) -> u16 {
73        let arg_len = match *self {
74            FieldInstr::Test { src: _ } => 1,
75            FieldInstr::Clr { dst: _ } => 1,
76            FieldInstr::PutD { dst: _, data: _ } => 3,
77            FieldInstr::PutZ { dst: _ } => 1,
78            FieldInstr::PutV { dst: _, val: _ } => 1,
79            FieldInstr::Fits { src: _, bits: _ } => 1,
80            FieldInstr::Mov { dst: _, src: _ } => 1,
81            FieldInstr::Eq { src1: _, src2: _ } => 1,
82            FieldInstr::Neg { dst: _, src: _ } => 1,
83            FieldInstr::Add { dst_src: _, src: _ } => 1,
84            FieldInstr::Mul { dst_src: _, src: _ } => 1,
85        };
86        arg_len + 1
87    }
88
89    fn external_ref(&self) -> Option<Id> { None }
90
91    fn encode_operands<W>(&self, writer: &mut W) -> Result<(), W::Error>
92    where W: BytecodeWrite<Id> {
93        match *self {
94            FieldInstr::Test { src } => {
95                writer.write_4bits(u4::with(SUB_TEST))?;
96                writer.write_4bits(src.to_u4())?;
97            }
98            FieldInstr::Clr { dst } => {
99                writer.write_4bits(u4::with(SUB_CLR))?;
100                writer.write_4bits(dst.to_u4())?;
101            }
102            FieldInstr::PutD { dst, data } => {
103                writer.write_4bits(u4::with(SUB_PUTD))?;
104                writer.write_4bits(dst.to_u4())?;
105                writer.write_fixed(data.to_u256().to_le_bytes())?;
106            }
107            FieldInstr::PutZ { dst } => {
108                writer.write_4bits(u4::with(SUB_PUTZ))?;
109                writer.write_4bits(dst.to_u4())?;
110            }
111            FieldInstr::PutV { dst, val } => {
112                let half = u4::with(TEST_PUTV | val.to_u2().to_u8());
113                writer.write_4bits(half)?;
114                writer.write_4bits(dst.to_u4())?;
115            }
116            FieldInstr::Fits { src, bits } => {
117                let half = u4::with(TEST_FITS | bits.to_u3().to_u8());
118                writer.write_4bits(half)?;
119                writer.write_4bits(src.to_u4())?;
120            }
121            FieldInstr::Mov { dst, src } => {
122                writer.write_4bits(dst.to_u4())?;
123                writer.write_4bits(src.to_u4())?;
124            }
125            FieldInstr::Eq { src1, src2 } => {
126                writer.write_4bits(src1.to_u4())?;
127                writer.write_4bits(src2.to_u4())?;
128            }
129            FieldInstr::Neg { dst, src } => {
130                writer.write_4bits(dst.to_u4())?;
131                writer.write_4bits(src.to_u4())?;
132            }
133            FieldInstr::Add { dst_src, src } => {
134                writer.write_4bits(dst_src.to_u4())?;
135                writer.write_4bits(src.to_u4())?;
136            }
137            FieldInstr::Mul { dst_src, src } => {
138                writer.write_4bits(dst_src.to_u4())?;
139                writer.write_4bits(src.to_u4())?;
140            }
141        }
142        Ok(())
143    }
144
145    fn decode_operands<R>(reader: &mut R, opcode: u8) -> Result<Self, CodeEofError>
146    where
147        Self: Sized,
148        R: BytecodeRead<Id>,
149    {
150        Ok(match opcode - Self::START {
151            Self::SET => {
152                let sub = u4::from(reader.read_4bits()?).to_u8();
153                match sub {
154                    SUB_TEST => {
155                        let src = RegE::from(reader.read_4bits()?);
156                        FieldInstr::Test { src }
157                    }
158                    SUB_CLR => {
159                        let dst = RegE::from(reader.read_4bits()?);
160                        FieldInstr::Clr { dst }
161                    }
162                    SUB_PUTD => {
163                        let dst = RegE::from(reader.read_4bits()?);
164                        let data = reader.read_fixed(|d: [u8; 32]| fe256::from(u256::from_le_bytes(d)))?;
165                        FieldInstr::PutD { dst, data }
166                    }
167                    SUB_PUTZ => {
168                        let dst = RegE::from(reader.read_4bits()?);
169                        FieldInstr::PutZ { dst }
170                    }
171                    x if x & MASK_PUTV == TEST_PUTV => {
172                        let val = ConstVal::from(u2::with(sub & !MASK_PUTV));
173                        let dst = RegE::from(reader.read_4bits()?);
174                        FieldInstr::PutV { dst, val }
175                    }
176                    x if x & MASK_FITS == TEST_FITS => {
177                        let bits = Bits::from(u3::with(sub & !MASK_FITS));
178                        let src = RegE::from(reader.read_4bits()?);
179                        FieldInstr::Fits { src, bits }
180                    }
181                    _ => unreachable!(),
182                }
183            }
184            Self::MOV => {
185                let dst = RegE::from(reader.read_4bits()?);
186                let src = RegE::from(reader.read_4bits()?);
187                FieldInstr::Mov { dst, src }
188            }
189            Self::EQ => {
190                let src1 = RegE::from(reader.read_4bits()?);
191                let src2 = RegE::from(reader.read_4bits()?);
192                FieldInstr::Eq { src1, src2 }
193            }
194            Self::NEG => {
195                let dst = RegE::from(reader.read_4bits()?);
196                let src = RegE::from(reader.read_4bits()?);
197                FieldInstr::Neg { dst, src }
198            }
199            Self::ADD => {
200                let dst_src = RegE::from(reader.read_4bits()?);
201                let src = RegE::from(reader.read_4bits()?);
202                FieldInstr::Add { dst_src, src }
203            }
204            Self::MUL => {
205                let dst_src = RegE::from(reader.read_4bits()?);
206                let src = RegE::from(reader.read_4bits()?);
207                FieldInstr::Mul { dst_src, src }
208            }
209            _ => unreachable!(),
210        })
211    }
212}
213
214impl<Id: SiteId> Bytecode<Id> for Instr<Id> {
215    fn op_range() -> RangeInclusive<u8> { 0..=0xFF }
216
217    fn opcode_byte(&self) -> u8 {
218        match self {
219            Instr::Ctrl(instr) => instr.opcode_byte(),
220            Instr::Gfa(instr) => Bytecode::<Id>::opcode_byte(instr),
221            Instr::Reserved(instr) => Bytecode::<Id>::opcode_byte(instr),
222        }
223    }
224
225    fn code_byte_len(&self) -> u16 {
226        match self {
227            Instr::Ctrl(instr) => instr.code_byte_len(),
228            Instr::Gfa(instr) => Bytecode::<Id>::code_byte_len(instr),
229            Instr::Reserved(instr) => Bytecode::<Id>::code_byte_len(instr),
230        }
231    }
232
233    fn external_ref(&self) -> Option<Id> {
234        match self {
235            Instr::Ctrl(instr) => instr.external_ref(),
236            Instr::Gfa(instr) => Bytecode::<Id>::external_ref(instr),
237            Instr::Reserved(instr) => Bytecode::<Id>::external_ref(instr),
238        }
239    }
240
241    fn encode_operands<W>(&self, writer: &mut W) -> Result<(), W::Error>
242    where W: BytecodeWrite<Id> {
243        match self {
244            Instr::Ctrl(instr) => instr.encode_operands(writer),
245            Instr::Gfa(instr) => instr.encode_operands(writer),
246            Instr::Reserved(instr) => instr.encode_operands(writer),
247        }
248    }
249
250    fn decode_operands<R>(reader: &mut R, opcode: u8) -> Result<Self, CodeEofError>
251    where
252        Self: Sized,
253        R: BytecodeRead<Id>,
254    {
255        match opcode {
256            op if CtrlInstr::<Id>::op_range().contains(&op) => {
257                CtrlInstr::<Id>::decode_operands(reader, op).map(Self::Ctrl)
258            }
259            op if <FieldInstr as Bytecode<Id>>::op_range().contains(&op) => {
260                FieldInstr::decode_operands(reader, op).map(Self::Gfa)
261            }
262            _ => ReservedInstr::decode_operands(reader, opcode).map(Self::Reserved),
263        }
264    }
265}
266
267#[cfg(test)]
268mod test {
269    use core::str::FromStr;
270
271    use aluvm::{LibId, LibsSeg, Marshaller};
272    use amplify::confinement::SmallBlob;
273
274    use super::*;
275    use crate::RegE;
276
277    const LIB_ID: &str = "5iMb1eHJ-bN5BOe6-9RvBjYL-jF1ELjj-VV7c8Bm-WvFen1Q";
278
279    fn roundtrip(instr: impl Into<Instr<LibId>>, bytecode: impl AsRef<[u8]>, dataseg: Option<&[u8]>) -> SmallBlob {
280        let instr = instr.into();
281        let mut libs = LibsSeg::new();
282        libs.push(LibId::from_str(LIB_ID).unwrap()).unwrap();
283        let mut marshaller = Marshaller::new(&libs);
284        instr.encode_instr(&mut marshaller).unwrap();
285        let (code, data) = marshaller.finish();
286        assert_eq!(code.as_slice(), bytecode.as_ref());
287        if let Some(d) = dataseg {
288            assert_eq!(data.as_slice(), d.as_ref());
289        } else {
290            assert!(data.is_empty());
291        }
292        let mut marshaller = Marshaller::with(code, data, &libs);
293        let decoded = Instr::<LibId>::decode_instr(&mut marshaller).unwrap();
294        assert_eq!(decoded, instr);
295        marshaller.into_code_data().1
296    }
297
298    #[test]
299    fn test() {
300        for reg in RegE::ALL {
301            let instr = FieldInstr::Test { src: reg };
302            let opcode = FieldInstr::START + FieldInstr::SET;
303            let sub = reg.to_u4().to_u8() << 4 | SUB_TEST;
304
305            roundtrip(instr, [opcode, sub], None);
306        }
307    }
308
309    #[test]
310    fn clr() {
311        for reg in RegE::ALL {
312            let instr = FieldInstr::Clr { dst: reg };
313            let opcode = FieldInstr::START + FieldInstr::SET;
314            let sub = reg.to_u4().to_u8() << 4 | SUB_CLR;
315
316            roundtrip(instr, [opcode, sub], None);
317        }
318    }
319
320    #[test]
321    fn putd() {
322        for reg in RegE::ALL {
323            let val = u256::from(0xdeadcafe1badbeef_u64);
324            let data = val.to_le_bytes();
325
326            let instr = FieldInstr::PutD {
327                dst: reg,
328                data: fe256::from(val),
329            };
330            let opcode = FieldInstr::START + FieldInstr::SET;
331            let sub = reg.to_u4().to_u8() << 4 | SUB_PUTD;
332
333            roundtrip(instr, [opcode, sub, 0, 0], Some(&data[..]));
334        }
335    }
336
337    #[test]
338    fn putz() {
339        for reg in RegE::ALL {
340            let instr = FieldInstr::PutZ { dst: reg };
341            let opcode = FieldInstr::START + FieldInstr::SET;
342            let sub = reg.to_u4().to_u8() << 4 | SUB_PUTZ;
343
344            roundtrip(instr, [opcode, sub], None);
345        }
346    }
347
348    #[test]
349    fn putv() {
350        for reg in RegE::ALL {
351            for val_u8 in 0..4 {
352                let val = ConstVal::from(u2::with(val_u8));
353                let instr = FieldInstr::PutV { dst: reg, val };
354                let opcode = FieldInstr::START + FieldInstr::SET;
355                let sub = reg.to_u4().to_u8() << 4 | TEST_PUTV | val.to_u2().to_u8();
356
357                roundtrip(instr, [opcode, sub], None);
358            }
359        }
360    }
361
362    #[test]
363    fn fits() {
364        for reg in RegE::ALL {
365            for bits_u8 in 0..8 {
366                let bits = Bits::from(u3::with(bits_u8));
367                let instr = FieldInstr::Fits { src: reg, bits };
368                let opcode = FieldInstr::START + FieldInstr::SET;
369                let sub = reg.to_u4().to_u8() << 4 | TEST_FITS | bits.to_u3().to_u8();
370
371                roundtrip(instr, [opcode, sub], None);
372            }
373        }
374    }
375
376    #[test]
377    fn mov() {
378        for reg1 in RegE::ALL {
379            for reg2 in RegE::ALL {
380                let instr = FieldInstr::Mov { dst: reg1, src: reg2 };
381                let opcode = FieldInstr::START + FieldInstr::MOV;
382                let regs = reg2.to_u4().to_u8() << 4 | reg1.to_u4().to_u8();
383
384                roundtrip(instr, [opcode, regs], None);
385            }
386        }
387    }
388
389    #[test]
390    fn eq() {
391        for reg1 in RegE::ALL {
392            for reg2 in RegE::ALL {
393                let instr = FieldInstr::Eq { src1: reg1, src2: reg2 };
394                let opcode = FieldInstr::START + FieldInstr::EQ;
395                let regs = reg2.to_u4().to_u8() << 4 | reg1.to_u4().to_u8();
396
397                roundtrip(instr, [opcode, regs], None);
398            }
399        }
400    }
401
402    #[test]
403    fn neq_mod() {
404        for reg1 in RegE::ALL {
405            for reg2 in RegE::ALL {
406                let instr = FieldInstr::Neg { dst: reg1, src: reg2 };
407                let opcode = FieldInstr::START + FieldInstr::NEG;
408                let regs = reg2.to_u4().to_u8() << 4 | reg1.to_u4().to_u8();
409
410                roundtrip(instr, [opcode, regs], None);
411            }
412        }
413    }
414
415    #[test]
416    fn add_mod() {
417        for reg1 in RegE::ALL {
418            for reg2 in RegE::ALL {
419                let instr = FieldInstr::Add {
420                    dst_src: reg1,
421                    src: reg2,
422                };
423                let opcode = FieldInstr::START + FieldInstr::ADD;
424                let regs = reg2.to_u4().to_u8() << 4 | reg1.to_u4().to_u8();
425
426                roundtrip(instr, [opcode, regs], None);
427            }
428        }
429    }
430
431    #[test]
432    fn mul_mod() {
433        for reg1 in RegE::ALL {
434            for reg2 in RegE::ALL {
435                let instr = FieldInstr::Mul {
436                    dst_src: reg1,
437                    src: reg2,
438                };
439                let opcode = FieldInstr::START + FieldInstr::MUL;
440                let regs = reg2.to_u4().to_u8() << 4 | reg1.to_u4().to_u8();
441
442                roundtrip(instr, [opcode, regs], None);
443            }
444        }
445    }
446}