Skip to content

Commit 9cef564

Browse files
committed
[Feat]: Add an 'in' instruction in the VM.
1 parent 619464c commit 9cef564

File tree

14 files changed

+752
-27
lines changed

14 files changed

+752
-27
lines changed

synthesizer/process/src/cost.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ pub fn cost_per_command<N: Network>(
423423
Command::Instruction(Instruction::HashManyPSD8(_)) => {
424424
bail!("`hash_many.psd8` is not supported in finalize")
425425
}
426+
Command::Instruction(Instruction::In(_)) => Ok(500),
426427
Command::Instruction(Instruction::Inv(_)) => Ok(2_500),
427428
Command::Instruction(Instruction::IsEq(_)) => Ok(500),
428429
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
@@ -736,6 +736,13 @@ impl<N: Network> FinalizeTypes<N> {
736736
"Instruction '{instruction}' has multiple destinations."
737737
);
738738
}
739+
Opcode::In => {
740+
// Ensure the instruction has one destination register.
741+
ensure!(
742+
instruction.destinations().len() == 1,
743+
"Instruction '{instruction}' has multiple destinations."
744+
);
745+
}
739746
}
740747
Ok(())
741748
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,13 @@ impl<N: Network> RegisterTypes<N> {
589589
"Instruction '{instruction}' has multiple destinations."
590590
);
591591
}
592+
Opcode::In => {
593+
// Ensure the instruction has one destination register.
594+
ensure!(
595+
instruction.destinations().len() == 1,
596+
"Instruction '{instruction}' has multiple destinations."
597+
);
598+
}
592599
}
593600
Ok(())
594601
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ pub enum Instruction<N: Network> {
136136
HashManyPSD4(HashManyPSD4<N>),
137137
/// Performs a Poseidon hash with an input rate of 8.
138138
HashManyPSD8(HashManyPSD8<N>),
139+
/// Returns true if the second i.e. the array contains the `first`, else returns false, storing the outcome in `destination`.
140+
In(In<N>),
139141
/// Computes the multiplicative inverse of `first`, storing the outcome in `destination`.
140142
Inv(Inv<N>),
141143
/// Computes whether `first` equals `second` as a boolean, storing the outcome in `destination`.
@@ -262,6 +264,7 @@ macro_rules! instruction {
262264
HashManyPSD2,
263265
HashManyPSD4,
264266
HashManyPSD8,
267+
In,
265268
Inv,
266269
IsEq,
267270
IsNeq,

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub enum Opcode {
3232
Commit(&'static str),
3333
/// The opcode is for a hash operation (i.e. `hash.psd4`).
3434
Hash(&'static str),
35+
/// The opcode is for an 'in' operation (i.e. `in`).
36+
In,
3537
/// The opcode is for an 'is' operation (i.e. `is.eq`).
3638
Is(&'static str),
3739
/// The opcode is for a literal operation (i.e. `add`).
@@ -54,6 +56,7 @@ impl Deref for Opcode {
5456
Opcode::Commit(opcode) => opcode,
5557
Opcode::Hash(opcode) => opcode,
5658
Opcode::Is(opcode) => opcode,
59+
Opcode::In => &"in",
5760
Opcode::Literal(opcode) => opcode,
5861
Opcode::Sign => &"sign.verify",
5962
}
@@ -78,6 +81,7 @@ impl Display for Opcode {
7881
Self::Command(opcode) => write!(f, "{opcode}"),
7982
Self::Commit(opcode) => write!(f, "{opcode}"),
8083
Self::Hash(opcode) => write!(f, "{opcode}"),
84+
Self::In => write!(f, "{}", self.deref()),
8185
Self::Is(opcode) => write!(f, "{opcode}"),
8286
Self::Literal(opcode) => write!(f, "{opcode}"),
8387
Self::Sign => write!(f, "{}", self.deref()),
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
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 output = (0..array.len()).any(|index| val.is_equal(&array[index]).eject_value());
110+
111+
// Store the output.
112+
registers.store_literal_circuit(
113+
stack,
114+
&self.destination,
115+
circuit::Literal::from(circuit::Boolean::constant(output)),
116+
)
117+
}
118+
119+
/// Finalizes the instruction.
120+
#[inline]
121+
pub fn finalize(&self, stack: &impl StackTrait<N>, registers: &mut impl RegistersTrait<N>) -> Result<()> {
122+
self.evaluate(stack, registers)
123+
}
124+
125+
/// Returns the output type from the given program and input types.
126+
pub fn output_types(
127+
&self,
128+
_stack: &impl StackTrait<N>,
129+
input_types: &[RegisterType<N>],
130+
) -> Result<Vec<RegisterType<N>>> {
131+
// Ensure the number of input types is correct.
132+
if input_types.len() != 2 {
133+
bail!("Instruction '{}' expects 2 inputs, found {} inputs", Self::opcode(), input_types.len())
134+
}
135+
136+
let RegisterType::Plaintext(PlaintextType::Array(arr_type)) = &input_types[1] else {
137+
bail!("Instruction {} expects the second input to be an array got {}", Self::opcode(), input_types[1])
138+
};
139+
140+
let element_type = RegisterType::Plaintext(arr_type.next_element_type().clone());
141+
142+
// Ensure the operand are of the same type.
143+
if input_types[0] != element_type {
144+
bail!(
145+
"Instruction '{}' expects first input and element type of array to be of the same type. Found inputs of type '{}' and '{}'",
146+
Self::opcode(),
147+
input_types[0],
148+
element_type
149+
)
150+
}
151+
152+
Ok(vec![RegisterType::Plaintext(PlaintextType::Literal(LiteralType::Boolean))])
153+
}
154+
}
155+
156+
impl<N: Network> Parser for In<N> {
157+
/// Parses a string into an operation.
158+
fn parse(string: &str) -> ParserResult<Self> {
159+
// Parse the opcode from the string.
160+
let (string, _) = tag(*Self::opcode())(string)?;
161+
// Parse the whitespace from the string.
162+
let (string, _) = Sanitizer::parse_whitespaces(string)?;
163+
// Parse the first operand from the string.
164+
let (string, first) = Operand::parse(string)?;
165+
// Parse the whitespace from the string.
166+
let (string, _) = Sanitizer::parse_whitespaces(string)?;
167+
// Parse the second operand from the string.
168+
let (string, second) = Operand::parse(string)?;
169+
// Parse the whitespace from the string.
170+
let (string, _) = Sanitizer::parse_whitespaces(string)?;
171+
// Parse the "into" from the string.
172+
let (string, _) = tag("into")(string)?;
173+
// Parse the whitespace from the string.
174+
let (string, _) = Sanitizer::parse_whitespaces(string)?;
175+
// Parse the destination register from the string.
176+
let (string, destination) = Register::parse(string)?;
177+
178+
Ok((string, Self { operands: [first, second], destination }))
179+
}
180+
}
181+
182+
impl<N: Network> FromStr for In<N> {
183+
type Err = Error;
184+
185+
/// Parses a string into an operation.
186+
fn from_str(string: &str) -> Result<Self> {
187+
match Self::parse(string) {
188+
Ok((remainder, object)) => {
189+
// Ensure the remainder is empty.
190+
ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
191+
// Return the object.
192+
Ok(object)
193+
}
194+
Err(error) => bail!("Failed to parse string. {error}"),
195+
}
196+
}
197+
}
198+
199+
impl<N: Network> Debug for In<N> {
200+
/// Prints the operation as a string.
201+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
202+
Display::fmt(self, f)
203+
}
204+
}
205+
206+
impl<N: Network> Display for In<N> {
207+
/// Prints the operation to a string.
208+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
209+
// Print the operation.
210+
write!(f, "{} ", Self::opcode())?;
211+
self.operands.iter().try_for_each(|operand| write!(f, "{operand} "))?;
212+
write!(f, "into {}", self.destination)
213+
}
214+
}
215+
216+
impl<N: Network> FromBytes for In<N> {
217+
/// Reads the operation from a buffer.
218+
fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
219+
// Initialize the array and read the operands.
220+
let operands = [Operand::read_le(&mut reader)?, Operand::read_le(&mut reader)?];
221+
222+
// Read the destination register.
223+
let destination = Register::read_le(&mut reader)?;
224+
225+
// Return the operation.
226+
Ok(Self { operands, destination })
227+
}
228+
}
229+
230+
impl<N: Network> ToBytes for In<N> {
231+
/// Writes the operation to a buffer.
232+
fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
233+
// Write the operands.
234+
self.operands.iter().try_for_each(|operand| operand.write_le(&mut writer))?;
235+
// Write the destination register.
236+
self.destination.write_le(&mut writer)
237+
}
238+
}
239+
240+
#[cfg(test)]
241+
mod tests {
242+
use super::*;
243+
use console::network::MainnetV0;
244+
245+
type CurrentNetwork = MainnetV0;
246+
247+
#[test]
248+
fn test_parse() {
249+
let (string, in_) = In::<CurrentNetwork>::parse("in r0 r1 into r2").unwrap();
250+
assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
251+
assert_eq!(in_.operands.len(), 2, "The number of operands is incorrect");
252+
assert_eq!(in_.operands[0], Operand::Register(Register::Locator(0)), "The first operand is incorrect");
253+
assert_eq!(in_.operands[1], Operand::Register(Register::Locator(1)), "The second operand is incorrect");
254+
assert_eq!(in_.destination, Register::Locator(2), "The destination register is incorrect");
255+
}
256+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ pub use commit::*;
3131
mod hash;
3232
pub use hash::*;
3333

34+
mod in_instruction;
35+
pub use in_instruction::*;
36+
3437
mod is;
3538
pub use is::*;
3639

synthesizer/program/tests/helpers/sample.rs

Lines changed: 7 additions & 7 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, Literal, 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: &[(&Literal<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(
@@ -38,11 +38,11 @@ pub fn sample_registers(
3838
);
3939

4040
// For each value, store the register and value.
41-
for (index, (literal, mode)) in values.iter().enumerate() {
41+
for (index, (value, mode)) in values.iter().enumerate() {
4242
// Initialize the register.
4343
let register = Register::Locator(index as u64);
4444
// Initialize the console value.
45-
let value = Value::Plaintext(Plaintext::from(*literal));
45+
let value = (*value).clone();
4646
// Store the value in the console registers.
4747
registers.store(stack, &register, value.clone())?;
4848
// If the mode is not `None`,
@@ -62,7 +62,7 @@ pub fn sample_registers(
6262
pub fn sample_finalize_registers(
6363
stack: &Stack<CurrentNetwork>,
6464
function_name: &Identifier<CurrentNetwork>,
65-
literals: &[&Literal<CurrentNetwork>],
65+
literals: &[&Value<CurrentNetwork>],
6666
) -> Result<FinalizeRegisters<CurrentNetwork>> {
6767
// Initialize the registers.
6868
let mut finalize_registers = FinalizeRegisters::<CurrentNetwork>::new(
@@ -74,11 +74,11 @@ pub fn sample_finalize_registers(
7474
);
7575

7676
// For each literal,
77-
for (index, literal) in literals.iter().enumerate() {
77+
for (index, val) in literals.iter().enumerate() {
7878
// Initialize the register
7979
let register = Register::Locator(index as u64);
8080
// Initialize the console value.
81-
let value = Value::Plaintext(Plaintext::from(*literal));
81+
let value = (*val).clone();
8282
// Store the value in the console registers.
8383
finalize_registers.store(stack, &register, value)?;
8484
}

0 commit comments

Comments
 (0)