Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ohadn/qm31 operations #1938

Open
wants to merge 1 commit into
base: ohadn/qm31_arithmetics-in-math_utils
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: remove `NonZeroReservedBits` from `VirtualMachineError` [#1948](https://github.com/lambdaclass/cairo-vm/pull/1948)
Expand Down
181 changes: 181 additions & 0 deletions cairo_programs/stwo_exclusive_programs/qm31_opcodes_test.cairo
Original file line number Diff line number Diff line change
@@ -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];
}
8 changes: 8 additions & 0 deletions vm/src/tests/cairo_run_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions vm/src/types/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub enum Opcode {
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum OpcodeExtension {
Stone,
QM31Operation,
}

impl Instruction {
Expand Down
25 changes: 25 additions & 0 deletions vm/src/vm/decoding/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@

let opcode_extension = match opcode_extension_num {
0 => 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,
Expand Down Expand Up @@ -423,4 +433,19 @@
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!(

Check warning on line 446 in vm/src/vm/decoding/decoder.rs

View check run for this annotation

Codecov / codecov/patch

vm/src/vm/decoding/decoder.rs#L446

Added line #L446 was not covered by tests
error,
Err(VirtualMachineError::InvalidQM31AddMulFlags(0x1108))
);
}
}
6 changes: 6 additions & 0 deletions vm/src/vm/errors/vm_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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)]
Expand Down Expand Up @@ -136,6 +140,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)]
Expand Down
Loading
Loading