Skip to content

Commit 2e6edbd

Browse files
committed
[Feat]: Add an 'in' instruction in the VM.
1 parent 6ef4e61 commit 2e6edbd

File tree

16 files changed

+759
-77
lines changed

16 files changed

+759
-77
lines changed

synthesizer/process/src/cost.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,7 @@ pub fn cost_per_command<N: Network>(
747747
Command::Instruction(Instruction::HashManyPSD8(_)) => {
748748
bail!("`hash_many.psd8` is not supported in finalize")
749749
}
750+
Command::Instruction(Instruction::In(_)) => Ok(500),
750751
Command::Instruction(Instruction::Inv(_)) => Ok(2_500),
751752
Command::Instruction(Instruction::IsEq(_)) => Ok(500),
752753
Command::Instruction(Instruction::IsNeq(_)) => Ok(500),

synthesizer/process/src/stack/finalize_types/initialize.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,13 @@ impl<N: Network> FinalizeTypes<N> {
739739
Opcode::ECDSA(opcode) => RegisterTypes::check_ecdsa_opcode(opcode, instruction)?,
740740
Opcode::Serialize(opcode) => RegisterTypes::check_serialize_opcode(opcode, instruction)?,
741741
Opcode::Deserialize(opcode) => RegisterTypes::check_deserialize_opcode(opcode, instruction)?,
742+
Opcode::In => {
743+
// Ensure the instruction has one destination register.
744+
ensure!(
745+
instruction.destinations().len() == 1,
746+
"Instruction '{instruction}' has multiple destinations."
747+
);
748+
}
742749
}
743750
Ok(())
744751
}

synthesizer/process/src/stack/register_types/initialize.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,13 @@ impl<N: Network> RegisterTypes<N> {
594594
}
595595
Opcode::Serialize(opcode) => Self::check_serialize_opcode(opcode, instruction)?,
596596
Opcode::Deserialize(opcode) => Self::check_deserialize_opcode(opcode, instruction)?,
597+
Opcode::In => {
598+
// Ensure the instruction has one destination register.
599+
ensure!(
600+
instruction.destinations().len() == 1,
601+
"Instruction '{instruction}' has multiple destinations."
602+
);
603+
}
597604
}
598605
Ok(())
599606
}

synthesizer/program/src/logic/instruction/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ pub enum Instruction<N: Network> {
234234
HashManyPSD4(HashManyPSD4<N>),
235235
/// Performs a Poseidon hash with an input rate of 8.
236236
HashManyPSD8(HashManyPSD8<N>),
237+
/// Returns true if the second i.e. the array contains the `first`, else returns false, storing the outcome in `destination`.
238+
In(In<N>),
237239
/// Computes the multiplicative inverse of `first`, storing the outcome in `destination`.
238240
Inv(Inv<N>),
239241
/// Computes whether `first` equals `second` as a boolean, storing the outcome in `destination`.
@@ -365,6 +367,7 @@ macro_rules! instruction {
365367
HashManyPSD2,
366368
HashManyPSD4,
367369
HashManyPSD8,
370+
In,
368371
Inv,
369372
IsEq,
370373
IsNeq,

synthesizer/program/src/logic/instruction/opcode/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ pub enum Opcode {
3434
Deserialize(&'static str),
3535
/// The opcode is for a hash operation (i.e. `hash.psd4`).
3636
Hash(&'static str),
37+
/// The opcode is for an 'in' operation (i.e. `in`).
38+
In,
3739
/// The opcode is for an 'is' operation (i.e. `is.eq`).
3840
Is(&'static str),
3941
/// The opcode is for a literal operation (i.e. `add`).
@@ -61,6 +63,7 @@ impl Deref for Opcode {
6163
Opcode::Deserialize(opcode) => opcode,
6264
Opcode::Hash(opcode) => opcode,
6365
Opcode::Is(opcode) => opcode,
66+
Opcode::In => &"in",
6467
Opcode::Literal(opcode) => opcode,
6568
Opcode::Serialize(opcode) => opcode,
6669
Opcode::Sign(opcode) => opcode,
@@ -88,6 +91,7 @@ impl Display for Opcode {
8891
Self::Commit(opcode) => write!(f, "{opcode}"),
8992
Self::Deserialize(opcode) => write!(f, "{opcode}"),
9093
Self::Hash(opcode) => write!(f, "{opcode}"),
94+
Self::In => write!(f, "{}", self.deref()),
9195
Self::Is(opcode) => write!(f, "{opcode}"),
9296
Self::Literal(opcode) => write!(f, "{opcode}"),
9397
Self::Serialize(opcode) => write!(f, "{opcode}"),
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
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+
16+
use circuit::{Eject, Inject};
17+
use console::{
18+
network::prelude::*,
19+
program::{Literal, LiteralType, Plaintext, PlaintextType, Register, RegisterType, Value},
20+
types::Boolean,
21+
};
22+
23+
use crate::{Opcode, Operand, RegistersCircuit, RegistersTrait, StackTrait};
24+
25+
/// Computes an equality operation on two operands, and stores the outcome in `destination`.
26+
#[derive(Clone, PartialEq, Eq, Hash)]
27+
pub struct In<N: Network> {
28+
/// The operands.
29+
operands: [Operand<N>; 2],
30+
/// The destination register.
31+
destination: Register<N>,
32+
}
33+
34+
impl<N: Network> In<N> {
35+
/// Initializes a new `in` instruction.
36+
#[inline]
37+
pub fn new(operands: [Operand<N>; 2], destination: Register<N>) -> Result<Self> {
38+
// Return the instruction.
39+
Ok(Self { operands, destination })
40+
}
41+
42+
/// Returns the opcode.s
43+
#[inline]
44+
pub const fn opcode() -> Opcode {
45+
Opcode::In
46+
}
47+
48+
/// Returns the operands in the operation.
49+
#[inline]
50+
pub fn operands(&self) -> &[Operand<N>] {
51+
// Return the operands.
52+
&self.operands
53+
}
54+
55+
/// Returns the destination register.
56+
#[inline]
57+
pub fn destinations(&self) -> Vec<Register<N>> {
58+
vec![self.destination.clone()]
59+
}
60+
}
61+
62+
impl<N: Network> In<N> {
63+
/// Evaluates the instruction.
64+
pub fn evaluate(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
65+
// Retrieve the inputs.
66+
let input_a = registers.load(stack, &self.operands[0])?;
67+
let input_b = registers.load(stack, &self.operands[1])?;
68+
69+
// Make sure the second operand is an array.
70+
let Value::Plaintext(Plaintext::Array(array, _)) = &input_b else {
71+
bail!("Instruction '{}' requires second operand to be an array but found {}", Self::opcode(), input_b)
72+
};
73+
74+
// Make sure the first operand is not an illegal type for this case.
75+
let Value::Plaintext(val) = &input_a else { bail!("Array cannot have records or futures as its elements.") };
76+
77+
// Check if the array contains the value.
78+
let output = Literal::Boolean(Boolean::new(array.contains(val)));
79+
80+
// Store the output.
81+
registers.store(stack, &self.destination, Value::Plaintext(Plaintext::from(output)))
82+
}
83+
84+
/// Executes the instruction.
85+
pub fn execute<A: circuit::Aleo<Network = N>>(
86+
&self,
87+
stack: &impl StackTrait<N>,
88+
registers: &mut impl RegistersCircuit<N, A>,
89+
) -> Result<()> {
90+
// Retrieve the inputs.
91+
let input_a = registers.load_circuit(stack, &self.operands[0])?;
92+
let input_b = registers.load_circuit(stack, &self.operands[1])?;
93+
94+
// Make sure the second operand is an array.
95+
let circuit::Value::Plaintext(circuit::Plaintext::Array(array, _)) = &input_b else {
96+
bail!(
97+
"Instruction '{}' requires second operand to be an array but found {}",
98+
Self::opcode(),
99+
input_b.eject_value()
100+
)
101+
};
102+
103+
// Make sure the first operand is not an illegal type for this case.
104+
let circuit::Value::Plaintext(val) = &input_a else {
105+
bail!("Array cannot have records or futures as its elements.")
106+
};
107+
108+
// Check if the array contains the value.
109+
let mut output = circuit::Boolean::constant(false);
110+
for element in array {
111+
output = val.is_equal(element).bitor(output)
112+
}
113+
114+
// Store the output.
115+
registers.store_literal_circuit(stack, &self.destination, circuit::Literal::from(output))
116+
}
117+
118+
/// Finalizes the instruction.
119+
#[inline]
120+
pub fn finalize(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
121+
self.evaluate(stack, registers)
122+
}
123+
124+
/// Returns the output type from the given program and input types.
125+
pub fn output_types(
126+
&self,
127+
_stack: &impl StackTrait<N>,
128+
input_types: &[RegisterType<N>],
129+
) -> Result<Vec<RegisterType<N>>> {
130+
// Ensure the number of input types is correct.
131+
if input_types.len() != 2 {
132+
bail!("Instruction '{}' expects 2 inputs, found {} inputs", Self::opcode(), input_types.len())
133+
}
134+
135+
let RegisterType::Plaintext(PlaintextType::Array(arr_type)) = &input_types[1] else {
136+
bail!("Instruction {} expects the second input to be an array got {}", Self::opcode(), input_types[1])
137+
};
138+
139+
let element_type = RegisterType::Plaintext(arr_type.next_element_type().clone());
140+
141+
// Ensure the operand are of the same type.
142+
if input_types[0] != element_type {
143+
bail!(
144+
"Instruction '{}' expects first input and element type of array to be of the same type. Found inputs of type '{}' and '{}'",
145+
Self::opcode(),
146+
input_types[0],
147+
element_type
148+
)
149+
}
150+
151+
Ok(vec![RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Boolean))])
152+
}
153+
}
154+
155+
impl<N: Network> Parser for In<N> {
156+
/// Parses a string into an operation.
157+
fn parse(string: &str) -> ParserResult<Self> {
158+
// Parse the opcode from the string.
159+
let (string, _) = tag(*Self::opcode())(string)?;
160+
// Parse the whitespace from the string.
161+
let (string, _) = Sanitizer::parse_whitespaces(string)?;
162+
// Parse the first operand from the string.
163+
let (string, first) = Operand::parse(string)?;
164+
// Parse the whitespace from the string.
165+
let (string, _) = Sanitizer::parse_whitespaces(string)?;
166+
// Parse the second operand from the string.
167+
let (string, second) = Operand::parse(string)?;
168+
// Parse the whitespace from the string.
169+
let (string, _) = Sanitizer::parse_whitespaces(string)?;
170+
// Parse the "into" from the string.
171+
let (string, _) = tag("into")(string)?;
172+
// Parse the whitespace from the string.
173+
let (string, _) = Sanitizer::parse_whitespaces(string)?;
174+
// Parse the destination register from the string.
175+
let (string, destination) = Register::parse(string)?;
176+
177+
Ok((string, Self { operands: [first, second], destination }))
178+
}
179+
}
180+
181+
impl<N: Network> FromStr for In<N> {
182+
type Err = Error;
183+
184+
/// Parses a string into an operation.
185+
fn from_str(string: &str) -> Result<Self> {
186+
match Self::parse(string) {
187+
Ok((remainder, object)) => {
188+
// Ensure the remainder is empty.
189+
ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
190+
// Return the object.
191+
Ok(object)
192+
}
193+
Err(error) => bail!("Failed to parse string. {error}"),
194+
}
195+
}
196+
}
197+
198+
impl<N: Network> Debug for In<N> {
199+
/// Prints the operation as a string.
200+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
201+
Display::fmt(self, f)
202+
}
203+
}
204+
205+
impl<N: Network> Display for In<N> {
206+
/// Prints the operation to a string.
207+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
208+
// Print the operation.
209+
write!(f, "{} ", Self::opcode())?;
210+
self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
211+
write!(f, "into {}", self.destination)
212+
}
213+
}
214+
215+
impl<N: Network> FromBytes for In<N> {
216+
/// Reads the operation from a buffer.
217+
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
218+
// Initialize the array and read the operands.
219+
let operands = [Operand::read_le(&mut reader)?, Operand::read_le(&mut reader)?];
220+
221+
// Read the destination register.
222+
let destination = Register::read_le(&mut reader)?;
223+
224+
// Return the operation.
225+
Ok(Self { operands, destination })
226+
}
227+
}
228+
229+
impl<N: Network> ToBytes for In<N> {
230+
/// Writes the operation to a buffer.
231+
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
232+
// Write the operands.
233+
self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
234+
// Write the destination register.
235+
self.destination.write_le(&mut writer)
236+
}
237+
}
238+
239+
#[cfg(test)]
240+
mod tests {
241+
use super::*;
242+
use console::network::MainnetV0;
243+
244+
type CurrentNetwork = MainnetV0;
245+
246+
#[test]
247+
fn test_parse() {
248+
let (string, in_) = In::<CurrentNetwork>::parse("in r0 r1 into r2").unwrap();
249+
assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
250+
assert_eq!(in_.operands.len(), 2, "The number of operands is incorrect");
251+
assert_eq!(in_.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
252+
assert_eq!(in_.operands[1], Operand::Register(Register::Locator(1)), "The second operand is incorrect");
253+
assert_eq!(in_.destination, Register::Locator(2), "The destination register is incorrect");
254+
}
255+
}

synthesizer/program/src/logic/instruction/operation/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ pub use ecdsa_verify::*;
3737
mod hash;
3838
pub use hash::*;
3939

40+
mod in_instruction;
41+
pub use in_instruction::*;
42+
4043
mod is;
4144
pub use is::*;
4245

synthesizer/program/tests/helpers/sample.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use circuit::AleoV0;
1717
use console::{
1818
network::MainnetV0,
1919
prelude::*,
20-
program::{Identifier, Plaintext, Register, Value},
20+
program::{Identifier, Register, Value},
2121
};
2222
use snarkvm_synthesizer_process::{Authorization, CallStack, FinalizeRegisters, Registers, Stack};
2323
use snarkvm_synthesizer_program::{FinalizeGlobalState, RegistersCircuit as _, RegistersTrait as _};
@@ -29,7 +29,7 @@ type CurrentAleo = AleoV0;
2929
pub fn sample_registers(
3030
stack: &Stack<CurrentNetwork>,
3131
function_name: &Identifier<CurrentNetwork>,
32-
values: &[(Value<CurrentNetwork>, Option<circuit::Mode>)],
32+
values: &[(&Value<CurrentNetwork>, Option<circuit::Mode>)],
3333
) -> Result<Registers<CurrentNetwork, CurrentAleo>> {
3434
// Initialize the registers.
3535
let mut registers = Registers::<CurrentNetwork, CurrentAleo>::new(
@@ -41,6 +41,8 @@ pub fn sample_registers(
4141
for (index, (value, mode)) in values.iter().enumerate() {
4242
// Initialize the register.
4343
let register = Register::Locator(index as u64);
44+
// Initialize the console value.
45+
let value = (*value).clone();
4446
// Store the value in the console registers.
4547
registers.store(stack, &register, value.clone())?;
4648
// If the mode is not `None`,
@@ -60,7 +62,7 @@ pub fn sample_registers(
6062
pub fn sample_finalize_registers(
6163
stack: &Stack<CurrentNetwork>,
6264
function_name: &Identifier<CurrentNetwork>,
63-
plaintexts: &[Plaintext<CurrentNetwork>],
65+
literals: &[&Value<CurrentNetwork>],
6466
) -> Result<FinalizeRegisters<CurrentNetwork>> {
6567
// Initialize the registers.
6668
let mut finalize_registers = FinalizeRegisters::<CurrentNetwork>::new(
@@ -72,11 +74,13 @@ pub fn sample_finalize_registers(
7274
);
7375

7476
// For each literal,
75-
for (index, plaintext) in plaintexts.iter().enumerate() {
77+
for (index, val) in literals.iter().enumerate() {
7678
// Initialize the register
7779
let register = Register::Locator(index as u64);
80+
// Initialize the console value.
81+
let value = (*val).clone();
7882
// Store the value in the console registers.
79-
finalize_registers.store(stack, &register, Value::Plaintext(plaintext.clone()))?;
83+
finalize_registers.store(stack, &register, value)?;
8084
}
8185

8286
Ok(finalize_registers)

0 commit comments

Comments
 (0)