diff --git a/CHANGELOG.md b/CHANGELOG.md index a6fd8ff734..5025d3d5f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: implement an opcode that computes QM31 arithmetics (add, sub, mul, div) in the VM [#1938](https://github.com/lambdaclass/cairo-vm/pull/1938) + * feat: add functions that compute packed reduced qm31 arithmetics to `math_utils` [#1944](https://github.com/lambdaclass/cairo-vm/pull/1944) * feat: implement `Blake2sLastBlock` opcode in VM [#1932](https://github.com/lambdaclass/cairo-vm/pull/1932) diff --git a/cairo_programs/stwo_exclusive_programs/qm31_opcodes_test.cairo b/cairo_programs/stwo_exclusive_programs/qm31_opcodes_test.cairo new file mode 100644 index 0000000000..6b47bb079f --- /dev/null +++ b/cairo_programs/stwo_exclusive_programs/qm31_opcodes_test.cairo @@ -0,0 +1,181 @@ +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.bool import FALSE, TRUE + +// Tests the QM31_add_mul opcode runners using specific examples as reference. +// The test is comprised of 10 test cases, each of which tests a different combination of missing_operand, is_imm, and is_mul. +// is_mul determines whether the operation is a multiplication or an addition of QM31 elements. +// is_imm determines whether op1 is an immediate. +// missing_operand determines which operand is missing and needs to be computed by the VM (0 for dst, 1 for op0, 2 fo op1). +// the combination of is_imm=TRUE with missig_operand=2 is not tested because we do not use arithmetic opcodes to deduce immediates. +func main{}() { + let qm31_op0_coordinates_a = 0x544b2fba; + let qm31_op0_coordinates_b = 0x673cff77; + let qm31_op0_coordinates_c = 0x60713d44; + let qm31_op0_coordinates_d = 0x499602d2; + let qm31_op0 = qm31_op0_coordinates_a + qm31_op0_coordinates_b*(2**36) + qm31_op0_coordinates_c*(2**72) + qm31_op0_coordinates_d*(2**108); + + let qm31_op1_coordinates_a = 0x4b18de99; + let qm31_op1_coordinates_b = 0x55f6fb62; + let qm31_op1_coordinates_c = 0x6e2290d9; + let qm31_op1_coordinates_d = 0x7cd851b9; + let qm31_op1 = qm31_op1_coordinates_a + qm31_op1_coordinates_b*(2**36) + qm31_op1_coordinates_c*(2**72) + qm31_op1_coordinates_d*(2**108); + + let qm31_add_dst_coordinates_a = 0x1f640e54; + let qm31_add_dst_coordinates_b = 0x3d33fada; + let qm31_add_dst_coordinates_c = 0x4e93ce1e; + let qm31_add_dst_coordinates_d = 0x466e548c; + let qm31_add_dst = qm31_add_dst_coordinates_a + qm31_add_dst_coordinates_b*(2**36) + qm31_add_dst_coordinates_c*(2**72) + qm31_add_dst_coordinates_d*(2**108); + + let qm31_mul_dst_coordinates_a = 0x38810ab4; + let qm31_mul_dst_coordinates_b = 0x5a0fd30a; + let qm31_mul_dst_coordinates_c = 0x2527b81e; + let qm31_mul_dst_coordinates_d = 0x4b1ed1cd; + let qm31_mul_dst = qm31_mul_dst_coordinates_a + qm31_mul_dst_coordinates_b*(2**36) + qm31_mul_dst_coordinates_c*(2**72) + qm31_mul_dst_coordinates_d*(2**108); + + let runner_output_mul_dst = run_qm31_operation(missing_operand=0, is_imm=FALSE, is_mul=TRUE, dst_or_op0=qm31_op0, op0_or_op1=qm31_op1); + assert runner_output_mul_dst = qm31_mul_dst; + let runner_output_add_dst = run_qm31_operation(missing_operand=0, is_imm=FALSE, is_mul=FALSE, dst_or_op0=qm31_op0, op0_or_op1=qm31_op1); + assert runner_output_add_dst = qm31_add_dst; + + let runner_output_mul_op0 = run_qm31_operation(missing_operand=1, is_imm=FALSE, is_mul=TRUE, dst_or_op0=qm31_mul_dst, op0_or_op1=qm31_op1); + assert runner_output_mul_op0 = qm31_op0; + let runner_output_add_op0 = run_qm31_operation(missing_operand=1, is_imm=FALSE, is_mul=FALSE, dst_or_op0=qm31_add_dst, op0_or_op1=qm31_op1); + assert runner_output_add_op0 = qm31_op0; + + let runner_output_mul_op1 = run_qm31_operation(missing_operand=2, is_imm=FALSE, is_mul=TRUE, dst_or_op0=qm31_mul_dst, op0_or_op1=qm31_op0); + assert runner_output_mul_op1 = qm31_op1; + let runner_output_add_op1 = run_qm31_operation(missing_operand=2, is_imm=FALSE, is_mul=FALSE, dst_or_op0=qm31_add_dst, op0_or_op1=qm31_op0); + assert runner_output_add_op1 = qm31_op1; + + let runner_output_mul_dst = run_qm31_operation(missing_operand=0, is_imm=TRUE, is_mul=TRUE, dst_or_op0=qm31_op0, op0_or_op1=qm31_op1); + assert runner_output_mul_dst = qm31_mul_dst; + let runner_output_add_dst = run_qm31_operation(missing_operand=0, is_imm=TRUE, is_mul=FALSE, dst_or_op0=qm31_op0, op0_or_op1=qm31_op1); + assert runner_output_add_dst = qm31_add_dst; + + let runner_output_mul_op0 = run_qm31_operation(missing_operand=1, is_imm=TRUE, is_mul=TRUE, dst_or_op0=qm31_mul_dst, op0_or_op1=qm31_op1); + assert runner_output_mul_op0 = qm31_op0; + let runner_output_add_op0 = run_qm31_operation(missing_operand=1, is_imm=TRUE, is_mul=FALSE, dst_or_op0=qm31_add_dst, op0_or_op1=qm31_op1); + assert runner_output_add_op0 = qm31_op0; + + return (); +} + +// Forces the runner to execute the QM31_add_mul opcode with the given operands. +// missing_operand, is_imm, is_mul determine the configuration of the operation as described above. +// dst_or_op0 is a felt representing the value of either the op0 (if missing_operand=0) or dst (otherwise) operand. +// op0_or_op1 is a felt representing the value of either the op0 (if missing_operand=2) or op1 (otherwise) operand. +// dst_or_op0 and op0_or_op1 are stored within addresses fp-4 and fp-3 respectively, they are passed to the instruction +// using offsets wrt fp (unless is_imm=TRUE, in which case op1 has offset 1 relative to pc). +// The missing operand has offset 0 relative to ap. +// An instruction encoding with the appropriate flags and offsets is built, then written to [pc] and the runner is forced to execute QM31_add_mul. +// The missing operand is deduced to [ap] and returned. +func run_qm31_operation( + missing_operand: felt, + is_imm: felt, + is_mul: felt, + dst_or_op0: felt, + op0_or_op1: felt, +) -> felt { + alloc_locals; + + // Set flags and offsets. + let (local offsets) = alloc(); + let (local flags) = alloc(); + + assert offsets[missing_operand] = 2**15; // the missing operand will be written to [ap] + + assert flags[2] = is_imm; // flag_op1_imm = 0; + assert flags[5] = 1-is_mul; // flag_res_add = 1-is_mul; + assert flags[6] = is_mul; // flag_res_mul = is_mul; + assert flags[7] = 0; // flag_PC_update_jump = 0; + assert flags[8] = 0; // flag_PC_update_jump_rel = 0; + assert flags[9] = 0; // flag_PC_update_jnz = 0; + assert flags[10] = 0; // flag_ap_update_add = 0; + assert flags[11] = 0; // flag_ap_update_add_1 = 0; + assert flags[12] = 0; // flag_opcode_call = 0; + assert flags[13] = 0; // flag_opcode_ret = 0; + assert flags[14] = 1; // flag_opcode_assert_eq = 1; + + if (missing_operand == 0) { + assert offsets[1] = 2**15 - 4; + assert offsets[2] = 2**15 - 3 + 4 * is_imm; + assert flags[0] = 0; // flag_dst_base_fp + assert flags[1] = 1; // flag_op0_base_fp + } + if (missing_operand == 1) { + assert offsets[0] = 2**15 - 4; + assert offsets[2] = 2**15 - 3 + 4 * is_imm; + assert flags[0] = 1; // flag_dst_base_fp + assert flags[1] = 0; // flag_op0_base_fp + } + if (missing_operand == 2) { + assert is_imm = FALSE; + assert offsets[0] = 2**15 - 4; + assert offsets[1] = 2**15 - 3; + assert flags[0] = 1; // flag_dst_base_fp + assert flags[1] = 1; // flag_op0_base_fp + } + assert flags[3] = 2 - is_imm - flags[0] - flags[1]; // flag_op1_base_fp + assert flags[4] = 1 - is_imm - flags[3]; // flag_op1_base_ap + + // Compute the instruction encoding. + let flag_num = flags[0] + flags[1]*(2**1) + flags[2]*(2**2) + flags[3]*(2**3) + flags[4]*(2**4) + flags[5]*(2**5) + flags[6]*(2**6) + flags[14]*(2**14); + let qm31_opcode_extension_num = 3; + let instruction_encoding = offsets[0] + offsets[1]*(2**16) + offsets[2]*(2**32) + flag_num*(2**48) + qm31_opcode_extension_num*(2**63); + + // Run the instruction and return the result. + if (is_imm == TRUE) { + assert op0_or_op1 = 0x7cd851b906e2290d9055f6fb6204b18de99; + if (missing_operand == 0) { + if (is_mul == TRUE) { + assert instruction_encoding=0x1c04680017ffc8000; + dw 0x1c04680017ffc8000; + dw 0x7cd851b906e2290d9055f6fb6204b18de99; + return [ap]; + } + assert instruction_encoding=0x1c02680017ffc8000; + dw 0x1c02680017ffc8000; + dw 0x7cd851b906e2290d9055f6fb6204b18de99; + return [ap]; + } + if (missing_operand == 1) { + if (is_mul == TRUE) { + assert instruction_encoding=0x1c045800180007ffc; + dw 0x1c045800180007ffc; + dw 0x7cd851b906e2290d9055f6fb6204b18de99; + return [ap]; + } + assert instruction_encoding=0x1c025800180007ffc; + dw 0x1c025800180007ffc; + dw 0x7cd851b906e2290d9055f6fb6204b18de99; + return [ap]; + } + } + + if (missing_operand == 0) { + if (is_mul == TRUE) { + assert instruction_encoding=0x1c04a7ffd7ffc8000; + dw 0x1c04a7ffd7ffc8000; + return [ap]; + } + assert instruction_encoding=0x1c02a7ffd7ffc8000; + dw 0x1c02a7ffd7ffc8000; + return [ap]; + } + if (missing_operand == 1) { + if (is_mul == TRUE) { + assert instruction_encoding=0x1c0497ffd80007ffc; + dw 0x1c0497ffd80007ffc; + return [ap]; + } + assert instruction_encoding=0x1c0297ffd80007ffc; + dw 0x1c0297ffd80007ffc; + return [ap]; + } + if (is_mul == TRUE) { + dw 0x1c05380007ffd7ffc; + return [ap]; + } + dw 0x1c03380007ffd7ffc; + return [ap]; +} diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index cbce327c08..01524cc38c 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -576,6 +576,14 @@ fn blake2s_opcode_test() { run_program_simple(program_data.as_slice()); } +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn qm31_opcodes_test() { + let program_data = + include_bytes!("../../../cairo_programs/stwo_exclusive_programs/qm31_opcodes_test.json"); + run_program_simple(program_data.as_slice()); +} + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn relocate_segments() { diff --git a/vm/src/types/errors/math_errors.rs b/vm/src/types/errors/math_errors.rs index ac5631cf3a..fee194a2de 100644 --- a/vm/src/types/errors/math_errors.rs +++ b/vm/src/types/errors/math_errors.rs @@ -42,6 +42,24 @@ pub enum MathError { RelocatableAddUsizeOffsetExceeded(Box<(Relocatable, usize)>), #[error("Operation failed: {} + {}, can't add two relocatable values", (*.0).0, (*.0).1)] RelocatableAdd(Box<(Relocatable, Relocatable)>), + #[error("Operation failed: {} - {}, can't add a relocatable value as a QM31 element", (*.0).0, (*.0).1)] + RelocatableQM31Add(Box<(MaybeRelocatable, MaybeRelocatable)>), + #[error("Operation failed: {} - {}, can't subtract a relocatable value or from a relocatable value as a QM31 element", (*.0).0, (*.0).1)] + RelocatableQM31Sub(Box<(MaybeRelocatable, MaybeRelocatable)>), + #[error("Operation failed: {} * {}, can't multiply a relocatable value", (*.0).0, (*.0).1)] + RelocatableMul(Box<(MaybeRelocatable, MaybeRelocatable)>), + #[error( + "Operation failed: typed_add for an OpcodeExtension that is neither Stone nor QM31Operation" + )] + InvalidAddOpcodeExtension(), + #[error( + "Operation failed: typed_mul for an OpcodeExtension that is neither Stone nor QM31Operation" + )] + InvalidSubOpcodeExtension(), + #[error( + "Operation failed: typed_sub for an OpcodeExtension that is neither Stone nor QM31Operation" + )] + InvalidMulOpcodeExtension(), #[error("Operation failed: {} - {}, can't subtract two relocatable values with different segment indexes", (*.0).0, (*.0).1)] RelocatableSubDiffIndex(Box<(Relocatable, Relocatable)>), #[error( diff --git a/vm/src/types/instruction.rs b/vm/src/types/instruction.rs index 439130b83c..9a8da5f299 100644 --- a/vm/src/types/instruction.rs +++ b/vm/src/types/instruction.rs @@ -82,6 +82,7 @@ pub enum OpcodeExtension { Stone, Blake, BlakeFinalize, + QM31Operation, } impl Instruction { diff --git a/vm/src/types/relocatable.rs b/vm/src/types/relocatable.rs index 7ca52eb45d..7e5ef52699 100644 --- a/vm/src/types/relocatable.rs +++ b/vm/src/types/relocatable.rs @@ -4,9 +4,14 @@ use crate::stdlib::{ prelude::*, }; +use crate::math_utils::{ + qm31_packed_reduced_add, qm31_packed_reduced_mul, qm31_packed_reduced_sub, +}; use crate::Felt252; use crate::{ - relocatable, types::errors::math_errors::MathError, vm::errors::memory_errors::MemoryError, + relocatable, + types::{errors::math_errors::MathError, instruction::OpcodeExtension}, + vm::errors::memory_errors::MemoryError, }; use num_traits::ToPrimitive; use serde::{Deserialize, Serialize}; @@ -330,6 +335,89 @@ impl MaybeRelocatable { } } + /// Substracts a MaybeRelocatable value from self according to the specified OpcodeExtension. + /// If the OpcodeExtension is Stone it subtracts as MaybeRelocatable::sub does. + /// If the OpcodeExtension is QM31Operation it requires them both to be Int and it subtracts + /// them as packed reduced QM31 elements. + pub fn typed_add( + &self, + other: &MaybeRelocatable, + opcode_extension: OpcodeExtension, + ) -> Result { + match opcode_extension { + OpcodeExtension::Stone => self.add(other), + OpcodeExtension::QM31Operation => { + if let (MaybeRelocatable::Int(num_self), MaybeRelocatable::Int(num_other)) = + (self, other) + { + Ok(MaybeRelocatable::Int(qm31_packed_reduced_add( + *num_self, *num_other, + )?)) + } else { + Err(MathError::RelocatableQM31Add(Box::new(( + self.clone(), + other.clone(), + )))) + } + } + _ => Err(MathError::InvalidAddOpcodeExtension()), + } + } + + /// Substracts a MaybeRelocatable value from self according to the specified OpcodeExtension. + /// If the OpcodeExtension is Stone it subtracts as MaybeRelocatable::sub does. + /// If the OpcodeExtension is QM31Operation it requires them both to be Int and it subtracts + /// them as packed reduced QM31 elements. + pub fn typed_sub( + &self, + other: &MaybeRelocatable, + opcode_extension: OpcodeExtension, + ) -> Result { + match opcode_extension { + OpcodeExtension::Stone => self.sub(other), + OpcodeExtension::QM31Operation => { + if let (MaybeRelocatable::Int(num_self), MaybeRelocatable::Int(num_other)) = + (self, other) + { + Ok(MaybeRelocatable::Int(qm31_packed_reduced_sub( + *num_self, *num_other, + )?)) + } else { + Err(MathError::RelocatableQM31Sub(Box::new(( + self.clone(), + other.clone(), + )))) + } + } + _ => Err(MathError::InvalidSubOpcodeExtension()), + } + } + + /// Multiplies self and another MaybeRelocatable value according to the specified OpcodeExtension. + /// Requires both operands to be Int. + /// If the OpcodeExtension is Stone it multiplies them as Felts. + /// If the OpcodeExtension is QM31Operation it multiplies them as packed reduced QM31 elements. + pub fn typed_mul( + &self, + other: &MaybeRelocatable, + opcode_extension: OpcodeExtension, + ) -> Result { + if let (MaybeRelocatable::Int(num_self), MaybeRelocatable::Int(num_other)) = (self, other) { + match opcode_extension { + OpcodeExtension::Stone => Ok(MaybeRelocatable::Int(num_self * num_other)), + OpcodeExtension::QM31Operation => Ok(MaybeRelocatable::Int( + qm31_packed_reduced_mul(*num_self, *num_other)?, + )), + _ => Err(MathError::InvalidMulOpcodeExtension()), + } + } else { + Err(MathError::RelocatableMul(Box::new(( + self.clone(), + other.clone(), + )))) + } + } + // TODO: Check if its more performant to use get_int instead /// Returns a reference to the inner value if it is a Felt252, returns None otherwise. pub fn get_int_ref(&self) -> Option<&Felt252> { diff --git a/vm/src/vm/decoding/decoder.rs b/vm/src/vm/decoding/decoder.rs index 85fa3e0495..0a561c226d 100644 --- a/vm/src/vm/decoding/decoder.rs +++ b/vm/src/vm/decoding/decoder.rs @@ -104,6 +104,17 @@ pub fn decode_instruction(encoded_instr: u128) -> Result OpcodeExtension::Stone, 1 => OpcodeExtension::Blake, 2 => OpcodeExtension::BlakeFinalize, + 3 => { + if (res != Res::Add && res != Res::Mul) + || op1_addr == Op1Addr::Op0 + || pc_update != PcUpdate::Regular + || opcode != Opcode::AssertEq + || (ap_update_num != 0 && ap_update_num != 2) + { + return Err(VirtualMachineError::InvalidQM31AddMulFlags(flags & 0x7FFF)); + } + OpcodeExtension::QM31Operation + } _ => { return Err(VirtualMachineError::InvalidOpcodeExtension( opcode_extension_num, @@ -479,9 +490,24 @@ mod decoder_test { // opcode_extension| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg // 79 ... 17 16 15| 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 // ???| CALL| Add2| JumpRel| Op1| IMM| FP| FP - // 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 - // 0001 1001 0001 0000 0100 = 0x39104; off0 = 0, off1 = 1 - let error = decode_instruction(0x19104800180018000); - assert_matches!(error, Err(VirtualMachineError::InvalidOpcodeExtension(3))); + // 1 1 1 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 + // 0011 1001 0001 0000 0100 = 0x39104; off0 = 0, off1 = 1 + let error = decode_instruction(0x39104800180018000); + assert_matches!(error, Err(VirtualMachineError::InvalidOpcodeExtension(7))); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn decode_qm31_operation_invalid_flags() { + // opcode_extension| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg + // 79 ... 17 16 15| 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 + // QM31Operation| CALL| REGULAR| JumpRel| Op1| FP| AP| AP + // 1 1 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 + // 1 1001 0001 0000 1000 = 0x19108; off0 = 1, off1 = 1 + let error = decode_instruction(0x19108800180018001); + assert_matches!( + error, + Err(VirtualMachineError::InvalidQM31AddMulFlags(0x1108)) + ); } } diff --git a/vm/src/vm/errors/vm_errors.rs b/vm/src/vm/errors/vm_errors.rs index 9162d591bd..d060ad596a 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -56,10 +56,6 @@ pub enum VirtualMachineError { UnconstrainedResAssertEq, #[error("A relocatable value as Res cannot be used with PcUpdate.JUMP_REL")] JumpRelNotInt, - #[error( - "Failed to compute Res.MUL: Could not complete computation of non pure values {} * {}", (*.0).0, (*.0).1 - )] - ComputeResRelocatableMul(Box<(MaybeRelocatable, MaybeRelocatable)>), #[error("Couldn't compute operand {}. Unknown value for memory cell {}", (*.0).0, (*.0).1)] FailedToComputeOperands(Box<(String, Relocatable)>), #[error("An ASSERT_EQ instruction failed: {} != {}.", (*.0).0, (*.0).1)] @@ -140,6 +136,8 @@ pub enum VirtualMachineError { Blake2sInvalidOperand(u8, u8), #[error("Blake2s opcode invalid flags {0}")] InvalidBlake2sFlags(u128), + #[error("QM31 add mul opcode invalid flags {0}")] + InvalidQM31AddMulFlags(u128), } #[cfg(test)] diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 87d9998138..39de4b1ac7 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -1,7 +1,6 @@ -use crate::math_utils::signed_felt; +use crate::math_utils::{qm31_packed_reduced_div, signed_felt}; use crate::stdlib::{any::Any, borrow::Cow, collections::HashMap, prelude::*}; use crate::types::builtin_name::BuiltinName; -use crate::types::instruction::OpcodeExtension; #[cfg(feature = "extensive_hints")] use crate::types::program::HintRange; use crate::{ @@ -13,7 +12,8 @@ use crate::{ errors::math_errors::MathError, exec_scope::ExecutionScopes, instruction::{ - is_call_instruction, ApUpdate, FpUpdate, Instruction, Opcode, PcUpdate, Res, + is_call_instruction, ApUpdate, FpUpdate, Instruction, Opcode, OpcodeExtension, + PcUpdate, Res, }, relocatable::{MaybeRelocatable, Relocatable}, }, @@ -237,19 +237,29 @@ impl VirtualMachine { None, )), Opcode::AssertEq => match (&instruction.res, dst, op1) { - (Res::Add, Some(dst_addr), Some(op1_addr)) => { - Ok((Some(dst_addr.sub(op1_addr)?), dst.cloned())) - } + (Res::Add, Some(dst_addr), Some(op1_addr)) => Ok(( + Some(dst_addr.typed_sub(op1_addr, instruction.opcode_extension)?), + dst.cloned(), + )), ( Res::Mul, Some(MaybeRelocatable::Int(num_dst)), Some(MaybeRelocatable::Int(num_op1)), - ) if !num_op1.is_zero() => Ok(( - Some(MaybeRelocatable::Int(num_dst.field_div( - &num_op1.try_into().map_err(|_| MathError::DividedByZero)?, - ))), - dst.cloned(), - )), + ) if !num_op1.is_zero() => { + let num_op0 = match instruction.opcode_extension { + OpcodeExtension::Stone => num_dst + .field_div(&num_op1.try_into().map_err(|_| MathError::DividedByZero)?), + OpcodeExtension::QM31Operation => { + qm31_packed_reduced_div(*num_dst, *num_op1)? + } + _ => { + return Err(VirtualMachineError::Math( + MathError::InvalidMulOpcodeExtension(), + )) + } + }; + Ok((Some(MaybeRelocatable::Int(num_op0)), dst.cloned())) + } _ => Ok((None, None)), }, _ => Ok((None, None)), @@ -270,7 +280,9 @@ impl VirtualMachine { Res::Op1 => return Ok((dst.cloned(), dst.cloned())), Res::Add => { return Ok(( - dst.zip(op0).and_then(|(dst, op0)| dst.sub(&op0).ok()), + dst.zip(op0).and_then(|(dst, op0)| { + dst.typed_sub(&op0, instruction.opcode_extension).ok() + }), dst.cloned(), )) } @@ -279,12 +291,20 @@ impl VirtualMachine { Some(MaybeRelocatable::Int(num_dst)), Some(MaybeRelocatable::Int(num_op0)), ) if !num_op0.is_zero() => { - return Ok(( - Some(MaybeRelocatable::Int(num_dst.field_div( + let num_op1 = match instruction.opcode_extension { + OpcodeExtension::Stone => num_dst.field_div( &num_op0.try_into().map_err(|_| MathError::DividedByZero)?, - ))), - dst.cloned(), - )) + ), + OpcodeExtension::QM31Operation => { + qm31_packed_reduced_div(*num_dst, num_op0)? + } + _ => { + return Err(VirtualMachineError::Math( + MathError::InvalidMulOpcodeExtension(), + )) + } + }; + return Ok((Some(MaybeRelocatable::Int(num_op1)), dst.cloned())); } _ => (), }, @@ -318,17 +338,8 @@ impl VirtualMachine { ) -> Result, VirtualMachineError> { match instruction.res { Res::Op1 => Ok(Some(op1.clone())), - Res::Add => Ok(Some(op0.add(op1)?)), - Res::Mul => { - if let (MaybeRelocatable::Int(num_op0), MaybeRelocatable::Int(num_op1)) = (op0, op1) - { - return Ok(Some(MaybeRelocatable::Int(num_op0 * num_op1))); - } - Err(VirtualMachineError::ComputeResRelocatableMul(Box::new(( - op0.clone(), - op1.clone(), - )))) - } + Res::Add => Ok(Some(op0.typed_add(op1, instruction.opcode_extension)?)), + Res::Mul => Ok(Some(op0.typed_mul(op1, instruction.opcode_extension)?)), Res::Unconstrained => Ok(None), } } @@ -2504,6 +2515,35 @@ mod tests { ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn deduce_op1_opcode_assert_eq_res_unconstrained_with_dst() { + let instruction = Instruction { + off0: 1, + off1: 2, + off2: 3, + dst_register: Register::FP, + op0_register: Register::AP, + op1_addr: Op1Addr::AP, + res: Res::Unconstrained, + pc_update: PcUpdate::Regular, + ap_update: ApUpdate::Regular, + fp_update: FpUpdate::Regular, + opcode: Opcode::AssertEq, + opcode_extension: OpcodeExtension::QM31Operation, + }; + + let vm = vm!(); + + let dst = MaybeRelocatable::Int(Felt252::from(7)); + assert_matches!( + vm.deduce_op1(&instruction, Some(&dst), None), + Ok::<(Option, Option), VirtualMachineError>(( + None, None + )) + ); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn compute_res_op1() { @@ -2618,7 +2658,35 @@ mod tests { let op0 = MaybeRelocatable::from((2, 6)); assert_matches!( vm.compute_res(&instruction, &op0, &op1), - Err(VirtualMachineError::ComputeResRelocatableMul(bx)) if *bx == (op0, op1) + Err(VirtualMachineError::Math(MathError::RelocatableMul(bx))) if *bx == (op0, op1) + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn compute_res_qm31_add_relocatable_values() { + let instruction = Instruction { + off0: 1, + off1: 2, + off2: 3, + dst_register: Register::FP, + op0_register: Register::AP, + op1_addr: Op1Addr::AP, + res: Res::Add, + pc_update: PcUpdate::Regular, + ap_update: ApUpdate::Regular, + fp_update: FpUpdate::Regular, + opcode: Opcode::AssertEq, + opcode_extension: OpcodeExtension::QM31Operation, + }; + + let vm = vm!(); + + let op1 = MaybeRelocatable::from((2, 3)); + let op0 = MaybeRelocatable::from((2, 6)); + assert_matches!( + vm.compute_res(&instruction, &op0, &op1), + Err(VirtualMachineError::Math(MathError::RelocatableQM31Add(bx))) if *bx == (op0, op1) ); }