diff --git a/CHANGELOG.md b/CHANGELOG.md index da51f5864c..cc9a267c00 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: set `encoded_instruction` to be u128 for opcode_extensions to come [#1940](https://github.com/lambdaclass/cairo-vm/pull/1940) 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..d9c240ea93 --- /dev/null +++ b/cairo_programs/stwo_exclusive_programs/qm31_opcodes_test.cairo @@ -0,0 +1,263 @@ +from starkware.cairo.common.bool import FALSE, TRUE + +func main{}() { + let qm31_op0_coordinates_a = 1414213562; + let qm31_op0_coordinates_b = 1732050807; + let qm31_op0_coordinates_c = 1618033988; + let qm31_op0_coordinates_d = 1234567890; + 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 = 1259921049; + let qm31_op1_coordinates_b = 1442249570; + let qm31_op1_coordinates_c = 1847759065; + let qm31_op1_coordinates_d = 2094551481; + 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); + static_assert qm31_op1==679720817185961464190715473544778505313945; + + + let qm31_add_dst_coordinates_a = 526650964; + let qm31_add_dst_coordinates_b = 1026816730; + let qm31_add_dst_coordinates_c = 1318309406; + let qm31_add_dst_coordinates_d = 1181635724; + 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 = 947980980; + let qm31_mul_dst_coordinates_b = 1510986506; + let qm31_mul_dst_coordinates_c = 623360030; + let qm31_mul_dst_coordinates_d = 1260310989; + 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_get_dst(is_mul=TRUE, op0=qm31_op0, op1=qm31_op1); + assert runner_output_mul_dst = qm31_mul_dst; + let runner_output_add_dst = run_qm31_operation_get_dst(is_mul=FALSE, op0=qm31_op0, op1=qm31_op1); + assert runner_output_add_dst = qm31_add_dst; + + let runner_output_mul_op1 = run_qm31_operation_get_op1(is_mul=TRUE, dst=qm31_mul_dst, op0=qm31_op0); + assert runner_output_mul_op1 = qm31_op1; + let runner_output_add_op1 = run_qm31_operation_get_op1(is_mul=FALSE, dst=qm31_add_dst, op0=qm31_op0); + assert runner_output_add_op1 = qm31_op1; + + let runner_output_mul_op0 = run_qm31_operation_get_op0(is_mul=TRUE, dst=qm31_mul_dst, op1=qm31_op1); + assert runner_output_mul_op0 = qm31_op0; + let runner_output_add_op0 = run_qm31_operation_get_op0(is_mul=FALSE, dst=qm31_add_dst, op1=qm31_op1); + assert runner_output_add_op0 = qm31_op0; + + let runner_output_mul_dst = run_qm31_operation_imm_op1_get_dst(is_mul=TRUE, op0=qm31_op0); + assert runner_output_mul_dst = qm31_mul_dst; + let runner_output_add_dst = run_qm31_operation_imm_op1_get_dst(is_mul=FALSE, op0=qm31_op0); + assert runner_output_add_dst = qm31_add_dst; + + let runner_output_mul_op0 = run_qm31_operation_imm_op1_get_op0(is_mul=TRUE, dst=qm31_mul_dst); + assert runner_output_mul_op0 = qm31_op0; + let runner_output_add_op0 = run_qm31_operation_imm_op1_get_op0(is_mul=FALSE, dst=qm31_add_dst); + assert runner_output_add_op0 = qm31_op0; + + return (); +} + +func run_qm31_operation_get_dst( + is_mul: felt, + op0: felt, + op1: felt, +) -> felt { + let offset0 = 2**15; + let offset1 = (2**15)-4; + let offset2 = (2**15)-3; + + let flag_dst_base_fp = 0; + let flag_op0_base_fp = 1; + let flag_op1_imm = 0; + let flag_op1_base_fp = 1; + let flag_op1_base_ap = 0; + let flag_res_add = 1-is_mul; + let flag_res_mul = is_mul; + let flag_PC_update_jump = 0; + let flag_PC_update_jump_rel = 0; + let flag_PC_update_jnz = 0; + let flag_ap_update_add = 0; + let flag_ap_update_add_1 = 0; + let flag_opcode_call = 0; + let flag_opcode_ret = 0; + let flag_opcode_assert_eq = 1; + + let flag_num_qm31_add = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+1*(2**5)+0*(2**6)+flag_opcode_assert_eq*(2**14); + let flag_num_qm31_mul = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+0*(2**5)+1*(2**6)+flag_opcode_assert_eq*(2**14); + let qm31_opcode_extension_num = 3; + let qm31_add_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_add*(2**48) + qm31_opcode_extension_num*(2**63); + let qm31_mul_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_mul*(2**48) + qm31_opcode_extension_num*(2**63); + static_assert qm31_mul_instruction_num==32302772004019011584; + static_assert qm31_add_instruction_num==32293764804764270592; + + if (is_mul == TRUE) { + dw 32302772004019011584; + return [ap]; + } + dw 32293764804764270592; + return [ap]; +} + +func run_qm31_operation_get_op1( + is_mul: felt, + dst: felt, + op0: felt, +) -> felt { + let offset0 = (2**15)-4; + let offset1 = (2**15)-3; + let offset2 = 2**15; + + let flag_dst_base_fp = 1; + let flag_op0_base_fp = 1; + let flag_op1_imm = 0; + let flag_op1_base_fp = 0; + let flag_op1_base_ap = 1; + let flag_res_add = 1-is_mul; + let flag_res_mul = is_mul; + let flag_PC_update_jump = 0; + let flag_PC_update_jump_rel = 0; + let flag_PC_update_jnz = 0; + let flag_ap_update_add = 0; + let flag_ap_update_add_1 = 0; + let flag_opcode_call = 0; + let flag_opcode_ret = 0; + let flag_opcode_assert_eq = 1; + + let flag_num_qm31_add = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+1*(2**5)+0*(2**6)+flag_opcode_assert_eq*(2**14); + let flag_num_qm31_mul = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+0*(2**5)+1*(2**6)+flag_opcode_assert_eq*(2**14); + let qm31_opcode_extension_num = 3; + let qm31_add_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_add*(2**48) + qm31_opcode_extension_num*(2**63); + let qm31_mul_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_mul*(2**48) + qm31_opcode_extension_num*(2**63); + static_assert qm31_mul_instruction_num==32305305291694374908; + static_assert qm31_add_instruction_num==32296298092439633916; + + if (is_mul == TRUE) { + dw 32305305291694374908; + return [ap]; + } + dw 32296298092439633916; + return [ap]; +} + +func run_qm31_operation_get_op0( + is_mul: felt, + dst: felt, + op1: felt, +) -> felt { + let offset0 = (2**15)-4; + let offset1 = 2**15; + let offset2 = (2**15)-3; + + let flag_dst_base_fp = 1; + let flag_op0_base_fp = 0; + let flag_op1_imm = 0; + let flag_op1_base_fp = 1; + let flag_op1_base_ap = 0; + let flag_res_add = 1-is_mul; + let flag_res_mul = is_mul; + let flag_PC_update_jump = 0; + let flag_PC_update_jump_rel = 0; + let flag_PC_update_jnz = 0; + let flag_ap_update_add = 0; + let flag_ap_update_add_1 = 0; + let flag_opcode_call = 0; + let flag_opcode_ret = 0; + let flag_opcode_assert_eq = 1; + + let flag_num_qm31_add = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+1*(2**5)+0*(2**6)+flag_opcode_assert_eq*(2**14); + let flag_num_qm31_mul = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+0*(2**5)+1*(2**6)+flag_opcode_assert_eq*(2**14); + let qm31_opcode_extension_num = 3; + let qm31_add_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_add*(2**48) + qm31_opcode_extension_num*(2**63); + let qm31_mul_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_mul*(2**48) + qm31_opcode_extension_num*(2**63); + static_assert qm31_mul_instruction_num==32302490529042563068; + static_assert qm31_add_instruction_num==32293483329787822076; + + if (is_mul == TRUE) { + dw 32302490529042563068; + return [ap]; + } + dw 32293483329787822076; + return [ap]; +} + +func run_qm31_operation_imm_op1_get_dst( + is_mul: felt, + op0: felt, +) -> felt { + let offset0 = 2**15; + let offset1 = (2**15)-3; + let offset2 = (2**15)+1; + + let flag_dst_base_fp = 0; + let flag_op0_base_fp = 1; + let flag_op1_imm = 1; + let flag_op1_base_fp = 0; + let flag_op1_base_ap = 0; + let flag_res_add = 1-is_mul; + let flag_res_mul = is_mul; + let flag_PC_update_jump = 0; + let flag_PC_update_jump_rel = 0; + let flag_PC_update_jnz = 0; + let flag_ap_update_add = 0; + let flag_ap_update_add_1 = 0; + let flag_opcode_call = 0; + let flag_opcode_ret = 0; + let flag_opcode_assert_eq = 1; + + let flag_num_qm31_add = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+1*(2**5)+0*(2**6)+flag_opcode_assert_eq*(2**14); + let flag_num_qm31_mul = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+0*(2**5)+1*(2**6)+flag_opcode_assert_eq*(2**14); + let qm31_opcode_extension_num = 3; + let qm31_add_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_add*(2**48) + qm31_opcode_extension_num*(2**63); + let qm31_mul_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_mul*(2**48) + qm31_opcode_extension_num*(2**63); + static_assert qm31_mul_instruction_num==32301646121292103680; + static_assert qm31_add_instruction_num==32292638922037362688; + + if (is_mul == TRUE) { + dw 32301646121292103680; + dw 679720817185961464190715473544778505313945; + return [ap]; + } + dw 32292638922037362688; + dw 679720817185961464190715473544778505313945; + return [ap]; +} + +func run_qm31_operation_imm_op1_get_op0( + is_mul: felt, + dst: felt, +) -> felt { + let offset0 = (2**15)-3; + let offset1 = 2**15; + let offset2 = (2**15)+1; + + let flag_dst_base_fp = 1; + let flag_op0_base_fp = 0; + let flag_op1_imm = 1; + let flag_op1_base_fp = 0; + let flag_op1_base_ap = 0; + let flag_res_add = 1-is_mul; + let flag_res_mul = is_mul; + let flag_PC_update_jump = 0; + let flag_PC_update_jump_rel = 0; + let flag_PC_update_jnz = 0; + let flag_ap_update_add = 0; + let flag_ap_update_add_1 = 0; + let flag_opcode_call = 0; + let flag_opcode_ret = 0; + let flag_opcode_assert_eq = 1; + + let flag_num_qm31_add = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+1*(2**5)+0*(2**6)+flag_opcode_assert_eq*(2**14); + let flag_num_qm31_mul = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+0*(2**5)+1*(2**6)+flag_opcode_assert_eq*(2**14); + let qm31_opcode_extension_num = 3; + let qm31_add_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_add*(2**48) + qm31_opcode_extension_num*(2**63); + let qm31_mul_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_mul*(2**48) + qm31_opcode_extension_num*(2**63); + static_assert qm31_mul_instruction_num==32301364646315589629; + static_assert qm31_add_instruction_num==32292357447060848637; + + if (is_mul == TRUE) { + dw 32301364646315589629; + dw 679720817185961464190715473544778505313945; + return [ap]; + } + dw 32292357447060848637; + dw 679720817185961464190715473544778505313945; + return [ap]; +} diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index c2dea2f4b2..f5a5668bed 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -568,6 +568,14 @@ fn blake2s_integration_tests() { 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/instruction.rs b/vm/src/types/instruction.rs index 8f598ccd6e..f0a363fec3 100644 --- a/vm/src/types/instruction.rs +++ b/vm/src/types/instruction.rs @@ -80,6 +80,7 @@ pub enum Opcode { #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum OpcodeExtension { Stone, + QM31Operation, } impl Instruction { diff --git a/vm/src/vm/decoding/decoder.rs b/vm/src/vm/decoding/decoder.rs index 70049cc2f5..0d841cae43 100644 --- a/vm/src/vm/decoding/decoder.rs +++ b/vm/src/vm/decoding/decoder.rs @@ -108,6 +108,16 @@ pub fn decode_instruction(encoded_instr: u128) -> Result OpcodeExtension::Stone, + 3 => { + if (res != Res::Add && res != Res::Mul) + || op1_addr == Op1Addr::Op0 + || pc_update != PcUpdate::Regular + || opcode != Opcode::AssertEq + { + return Err(VirtualMachineError::InvalidQM31AddMulFlags(flags & 0x7FFF)); + } + OpcodeExtension::QM31Operation + } _ => { return Err(VirtualMachineError::InvalidOpcodeExtension( opcode_extension_num, @@ -429,4 +439,19 @@ mod decoder_test { let error = decode_instruction(0x9104800180018000); assert_matches!(error, Err(VirtualMachineError::InvalidOpcodeExtension(1))); } + + #[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 3733c12270..1b6e9d531b 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -62,6 +62,10 @@ pub enum VirtualMachineError { "Failed to compute Res.MUL: Could not complete computation of non pure values {} * {}", (*.0).0, (*.0).1 )] ComputeResRelocatableMul(Box<(MaybeRelocatable, MaybeRelocatable)>), + #[error( + "Failed to compute Res.ADD for QM31Operation: Could not complete computation of non pure values {} * {}", (*.0).0, (*.0).1 + )] + ComputeResRelocatableQM31Add(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)] @@ -138,6 +142,8 @@ pub enum VirtualMachineError { RelocationNotFound(usize), #[error("{} batch size is not {}", (*.0).0, (*.0).1)] ModBuiltinBatchSize(Box<(BuiltinName, usize)>), + #[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 878953b9ea..d4d792b5ba 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -1,4 +1,7 @@ -use crate::math_utils::signed_felt; +use crate::math_utils::{ + qm31_packed_reduced_add, qm31_packed_reduced_div, qm31_packed_reduced_mul, + qm31_packed_reduced_sub, signed_felt, +}; use crate::stdlib::{any::Any, borrow::Cow, collections::HashMap, prelude::*}; use crate::types::builtin_name::BuiltinName; #[cfg(feature = "extensive_hints")] @@ -9,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}, }, @@ -232,20 +236,37 @@ impl VirtualMachine { )), None, )), - Opcode::AssertEq => match (&instruction.res, dst, op1) { - (Res::Add, Some(dst_addr), Some(op1_addr)) => { + Opcode::AssertEq => match (&instruction.res, dst, op1, &instruction.opcode_extension) { + (Res::Add, Some(dst_addr), Some(op1_addr), OpcodeExtension::Stone) => { Ok((Some(dst_addr.sub(op1_addr)?), dst.cloned())) } ( - Res::Mul, + Res::Add, 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)?, - ))), + OpcodeExtension::QM31Operation, + ) => Ok(( + Some(MaybeRelocatable::Int(qm31_packed_reduced_sub( + *num_dst, *num_op1, + )?)), dst.cloned(), )), + ( + Res::Mul, + Some(MaybeRelocatable::Int(num_dst)), + Some(MaybeRelocatable::Int(num_op1)), + OpcodeExtension::Stone | OpcodeExtension::QM31Operation, + ) if !num_op1.is_zero() => { + let op0_val = + if instruction.opcode_extension == OpcodeExtension::Stone { + MaybeRelocatable::Int(num_dst.field_div( + &num_op1.try_into().map_err(|_| MathError::DividedByZero)?, + )) + } else { + MaybeRelocatable::Int(qm31_packed_reduced_div(*num_dst, *num_op1)?) + }; + Ok((Some(op0_val), dst.cloned())) + } _ => Ok((None, None)), }, _ => Ok((None, None)), @@ -262,28 +283,47 @@ impl VirtualMachine { op0: Option, ) -> Result<(Option, Option), VirtualMachineError> { if let Opcode::AssertEq = instruction.opcode { - match instruction.res { - Res::Op1 => return Ok((dst.cloned(), dst.cloned())), - Res::Add => { + match (instruction.res, instruction.opcode_extension) { + (Res::Op1, OpcodeExtension::Stone) => return Ok((dst.cloned(), dst.cloned())), + (Res::Add, OpcodeExtension::Stone) => { return Ok(( dst.zip(op0).and_then(|(dst, op0)| dst.sub(&op0).ok()), dst.cloned(), )) } - Res::Mul => match (dst, op0) { - ( + (Res::Add, OpcodeExtension::QM31Operation) => { + if let ( Some(MaybeRelocatable::Int(num_dst)), Some(MaybeRelocatable::Int(num_op0)), - ) if !num_op0.is_zero() => { + ) = (dst, op0) + { return Ok(( - Some(MaybeRelocatable::Int(num_dst.field_div( - &num_op0.try_into().map_err(|_| MathError::DividedByZero)?, - ))), + Some(MaybeRelocatable::Int(qm31_packed_reduced_sub( + *num_dst, num_op0, + )?)), dst.cloned(), - )) + )); } - _ => (), - }, + } + (Res::Mul, OpcodeExtension::Stone | OpcodeExtension::QM31Operation) => { + match (dst, op0) { + ( + Some(MaybeRelocatable::Int(num_dst)), + Some(MaybeRelocatable::Int(num_op0)), + ) if !num_op0.is_zero() => { + let op1_val = if instruction.opcode_extension == OpcodeExtension::Stone + { + MaybeRelocatable::Int(num_dst.field_div( + &num_op0.try_into().map_err(|_| MathError::DividedByZero)?, + )) + } else { + MaybeRelocatable::Int(qm31_packed_reduced_div(*num_dst, num_op0)?) + }; + return Ok((Some(op1_val), dst.cloned())); + } + _ => (), + } + } _ => (), }; }; @@ -312,20 +352,37 @@ impl VirtualMachine { op0: &MaybeRelocatable, op1: &MaybeRelocatable, ) -> Result, VirtualMachineError> { - match instruction.res { - Res::Op1 => Ok(Some(op1.clone())), - Res::Add => Ok(Some(op0.add(op1)?)), - Res::Mul => { + match (instruction.res, instruction.opcode_extension) { + (Res::Op1, OpcodeExtension::Stone) => Ok(Some(op1.clone())), + (Res::Add, OpcodeExtension::Stone) => Ok(Some(op0.add(op1)?)), + (Res::Add, OpcodeExtension::QM31Operation) => { if let (MaybeRelocatable::Int(num_op0), MaybeRelocatable::Int(num_op1)) = (op0, op1) { - return Ok(Some(MaybeRelocatable::Int(num_op0 * num_op1))); + return Ok(Some(MaybeRelocatable::Int(qm31_packed_reduced_add( + *num_op0, *num_op1, + )?))); + } + Err(VirtualMachineError::ComputeResRelocatableQM31Add(Box::new( + (op0.clone(), op1.clone()), + ))) + } + (Res::Mul, OpcodeExtension::Stone | OpcodeExtension::QM31Operation) => { + if let (MaybeRelocatable::Int(num_op0), MaybeRelocatable::Int(num_op1)) = (op0, op1) + { + if instruction.opcode_extension == OpcodeExtension::Stone { + return Ok(Some(MaybeRelocatable::Int(num_op0 * num_op1))); + } else { + return Ok(Some(MaybeRelocatable::Int(qm31_packed_reduced_mul( + *num_op0, *num_op1, + )?))); + } } Err(VirtualMachineError::ComputeResRelocatableMul(Box::new(( op0.clone(), op1.clone(), )))) } - Res::Unconstrained => Ok(None), + _ => Ok(None), } } @@ -2438,6 +2495,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() { @@ -2556,6 +2642,34 @@ mod tests { ); } + #[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::ComputeResRelocatableQM31Add(bx)) if *bx == (op0, op1) + ); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn compute_res_unconstrained() {