Skip to main content

snarkvm_synthesizer_program/logic/instruction/operation/
is.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, StackTrait, register_types_equivalent};
17use console::{
18    network::prelude::*,
19    program::{Literal, LiteralType, Plaintext, PlaintextType, Register, RegisterType, Value},
20    types::Boolean,
21};
22
23/// Computes whether `first` equals `second` as a boolean, storing the outcome in `destination`.
24pub type IsEq<N> = IsInstruction<N, { Variant::IsEq as u8 }>;
25/// Computes whether `first` does **not** equals `second` as a boolean, storing the outcome in `destination`.
26pub type IsNeq<N> = IsInstruction<N, { Variant::IsNeq as u8 }>;
27
28enum Variant {
29    IsEq,
30    IsNeq,
31}
32
33/// Computes an equality operation on two operands, and stores the outcome in `destination`.
34#[derive(Clone, PartialEq, Eq, Hash)]
35pub struct IsInstruction<N: Network, const VARIANT: u8> {
36    /// The operands.
37    operands: Vec<Operand<N>>,
38    /// The destination register.
39    destination: Register<N>,
40}
41
42impl<N: Network, const VARIANT: u8> IsInstruction<N, VARIANT> {
43    /// Initializes a new `is` instruction.
44    pub fn new(operands: Vec<Operand<N>>, destination: Register<N>) -> Result<Self> {
45        // Sanity check the number of operands.
46        ensure!(operands.len() == 2, "Instruction '{}' must have two operands", Self::opcode());
47        // Return the instruction.
48        Ok(Self { operands, destination })
49    }
50
51    /// Returns the opcode.
52    pub const fn opcode() -> Opcode {
53        match VARIANT {
54            0 => Opcode::Is("is.eq"),
55            1 => Opcode::Is("is.neq"),
56            _ => panic!("Invalid 'is' instruction opcode"),
57        }
58    }
59
60    /// Returns the operands in the operation.
61    #[inline]
62    pub fn operands(&self) -> &[Operand<N>] {
63        // Sanity check that the operands is exactly two inputs.
64        debug_assert!(self.operands.len() == 2, "Instruction '{}' must have two operands", Self::opcode());
65        // Return the operands.
66        &self.operands
67    }
68
69    /// Returns the destination register.
70    #[inline]
71    pub fn destinations(&self) -> Vec<Register<N>> {
72        vec![self.destination.clone()]
73    }
74
75    /// Returns whether this instruction refers to an external struct.
76    #[inline]
77    pub fn contains_external_struct(&self) -> bool {
78        false
79    }
80}
81
82impl<N: Network, const VARIANT: u8> IsInstruction<N, VARIANT> {
83    /// Evaluates the instruction.
84    pub fn evaluate(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
85        // Ensure the number of operands is correct.
86        if self.operands.len() != 2 {
87            bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
88        }
89
90        // Retrieve the inputs.
91        let input_a = registers.load(stack, &self.operands[0])?;
92        let input_b = registers.load(stack, &self.operands[1])?;
93
94        // Check the inputs.
95        let output = match VARIANT {
96            0 => Literal::Boolean(Boolean::new(input_a == input_b)),
97            1 => Literal::Boolean(Boolean::new(input_a != input_b)),
98            _ => bail!("Invalid 'is' variant: {VARIANT}"),
99        };
100        // Store the output.
101        registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(output)))
102    }
103
104    /// Executes the instruction.
105    pub fn execute<A: circuit::Aleo<Network = N>>(
106        &self,
107        stack: &impl StackTrait<N>,
108        registers: &mut impl RegistersCircuit<N, A>,
109    ) -> Result<()> {
110        // Ensure the number of operands is correct.
111        if self.operands.len() != 2 {
112            bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
113        }
114
115        // Retrieve the inputs.
116        let input_a = registers.load_circuit(stack, &self.operands[0])?;
117        let input_b = registers.load_circuit(stack, &self.operands[1])?;
118
119        // Check the inputs.
120        let output = match VARIANT {
121            0 => circuit::Literal::Boolean(input_a.is_equal(&input_b)),
122            1 => circuit::Literal::Boolean(input_a.is_not_equal(&input_b)),
123            _ => bail!("Invalid 'is' variant: {VARIANT}"),
124        };
125        // Convert the output to a stack value.
126        let output = circuit::Value::Plaintext(circuit::Plaintext::Literal(output, Default::default()));
127        // Store the output.
128        registers.store_circuit(stack, &self.destination, output)
129    }
130
131    /// Finalizes the instruction.
132    #[inline]
133    pub fn finalize(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
134        self.evaluate(stack, registers)
135    }
136
137    /// Returns the output type from the given program and input types.
138    pub fn output_types(
139        &self,
140        stack: &impl StackTrait<N>,
141        input_types: &[RegisterType<N>],
142    ) -> Result<Vec<RegisterType<N>>> {
143        // Ensure the number of input types is correct.
144        if input_types.len() != 2 {
145            bail!("Instruction '{}' expects 2 inputs, found {} inputs", Self::opcode(), input_types.len())
146        }
147        // Ensure the operands are of the same type.
148        if !register_types_equivalent(stack, &input_types[0], stack, &input_types[1])? {
149            bail!(
150                "Instruction '{}' expects inputs of the same type. Found inputs of type '{}' and '{}'",
151                Self::opcode(),
152                input_types[0],
153                input_types[1]
154            )
155        }
156        // Ensure the number of operands is correct.
157        if self.operands.len() != 2 {
158            bail!("Instruction '{}' expects 2 operands, found {} operands", Self::opcode(), self.operands.len())
159        }
160
161        match VARIANT {
162            0 | 1 => Ok(vec![RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Boolean))]),
163            _ => bail!("Invalid 'is' variant: {VARIANT}"),
164        }
165    }
166}
167
168impl<N: Network, const VARIANT: u8> Parser for IsInstruction<N, VARIANT> {
169    /// Parses a string into an operation.
170    fn parse(string: &str) -> ParserResult<Self> {
171        // Parse the opcode from the string.
172        let (string, _) = tag(*Self::opcode())(string)?;
173        // Parse the whitespace from the string.
174        let (string, _) = Sanitizer::parse_whitespaces(string)?;
175        // Parse the first operand from the string.
176        let (string, first) = Operand::parse(string)?;
177        // Parse the whitespace from the string.
178        let (string, _) = Sanitizer::parse_whitespaces(string)?;
179        // Parse the second operand from the string.
180        let (string, second) = Operand::parse(string)?;
181        // Parse the whitespace from the string.
182        let (string, _) = Sanitizer::parse_whitespaces(string)?;
183        // Parse the "into" from the string.
184        let (string, _) = tag("into")(string)?;
185        // Parse the whitespace from the string.
186        let (string, _) = Sanitizer::parse_whitespaces(string)?;
187        // Parse the destination register from the string.
188        let (string, destination) = Register::parse(string)?;
189
190        Ok((string, Self { operands: vec![first, second], destination }))
191    }
192}
193
194impl<N: Network, const VARIANT: u8> FromStr for IsInstruction<N, VARIANT> {
195    type Err = Error;
196
197    /// Parses a string into an operation.
198    fn from_str(string: &str) -> Result<Self> {
199        match Self::parse(string) {
200            Ok((remainder, object)) => {
201                // Ensure the remainder is empty.
202                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
203                // Return the object.
204                Ok(object)
205            }
206            Err(error) => bail!("Failed to parse string. {error}"),
207        }
208    }
209}
210
211impl<N: Network, const VARIANT: u8> Debug for IsInstruction<N, VARIANT> {
212    /// Prints the operation as a string.
213    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
214        Display::fmt(self, f)
215    }
216}
217
218impl<N: Network, const VARIANT: u8> Display for IsInstruction<N, VARIANT> {
219    /// Prints the operation to a string.
220    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
221        // Ensure the number of operands is 2.
222        if self.operands.len() != 2 {
223            return Err(fmt::Error);
224        }
225        // Print the operation.
226        write!(f, "{} ", Self::opcode())?;
227        self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
228        write!(f, "into {}", self.destination)
229    }
230}
231
232impl<N: Network, const VARIANT: u8> FromBytes for IsInstruction<N, VARIANT> {
233    /// Reads the operation from a buffer.
234    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
235        // Initialize the vector for the operands.
236        let mut operands = Vec::with_capacity(2);
237        // Read the operands.
238        for _ in 0..2 {
239            operands.push(Operand::read_le(&mut reader)?);
240        }
241        // Read the destination register.
242        let destination = Register::read_le(&mut reader)?;
243
244        // Return the operation.
245        Ok(Self { operands, destination })
246    }
247}
248
249impl<N: Network, const VARIANT: u8> ToBytes for IsInstruction<N, VARIANT> {
250    /// Writes the operation to a buffer.
251    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
252        // Ensure the number of operands is 2.
253        if self.operands.len() != 2 {
254            return Err(error(format!("The number of operands must be 2, found {}", self.operands.len())));
255        }
256        // Write the operands.
257        self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
258        // Write the destination register.
259        self.destination.write_le(&mut writer)
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266    use console::network::MainnetV0;
267
268    type CurrentNetwork = MainnetV0;
269
270    #[test]
271    fn test_parse() {
272        let (string, is) = IsEq::<CurrentNetwork>::parse("is.eq r0 r1 into r2").unwrap();
273        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
274        assert_eq!(is.operands.len(), 2, "The number of operands is incorrect");
275        assert_eq!(is.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
276        assert_eq!(is.operands[1], Operand::Register(Register::Locator(1)), "The second operand is incorrect");
277        assert_eq!(is.destination, Register::Locator(2), "The destination register is incorrect");
278
279        let (string, is) = IsNeq::<CurrentNetwork>::parse("is.neq r0 r1 into r2").unwrap();
280        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
281        assert_eq!(is.operands.len(), 2, "The number of operands is incorrect");
282        assert_eq!(is.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
283        assert_eq!(is.operands[1], Operand::Register(Register::Locator(1)), "The second operand is incorrect");
284        assert_eq!(is.destination, Register::Locator(2), "The destination register is incorrect");
285    }
286}