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