Skip to content
Merged
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: implement `Blake2sLastBlock` opcode in VM [#1932](https://github.com/lambdaclass/cairo-vm/pull/1932)
Expand Down
183 changes: 183 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,183 @@
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 - flags[0] - flags[1]) * (1 - is_imm); // 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) {
assert instruction_encoding=0x1c05380007ffd7ffc;
dw 0x1c05380007ffd7ffc;
return [ap];
}
assert instruction_encoding=0x1c03380007ffd7ffc;
dw 0x1c03380007ffd7ffc;
return [ap];
}
1 change: 1 addition & 0 deletions vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub mod hint_processor;
pub mod math_utils;
pub mod program_hash;
pub mod serde;
pub mod typed_operations;
pub mod types;
pub mod utils;
pub mod vm;
Expand Down
4 changes: 2 additions & 2 deletions vm/src/math_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ lazy_static! {
.collect::<Vec<_>>();
}

const STWO_PRIME: u64 = (1 << 31) - 1;
pub const STWO_PRIME: u64 = (1 << 31) - 1;
const STWO_PRIME_U128: u128 = STWO_PRIME as u128;
const MASK_36: u64 = (1 << 36) - 1;
const MASK_8: u64 = (1 << 8) - 1;
Expand Down Expand Up @@ -101,7 +101,7 @@ fn qm31_packed_reduced_read_coordinates(felt: Felt252) -> Result<[u64; 4], MathE
/// Reduces four u64 coordinates and packs them into a single Felt252.
/// STWO_PRIME fits in 36 bits, hence each coordinate can be represented by 36 bits and a QM31
/// element can be stored in the first 144 bits of a Felt252.
fn qm31_coordinates_to_packed_reduced(coordinates: [u64; 4]) -> Felt252 {
pub fn qm31_coordinates_to_packed_reduced(coordinates: [u64; 4]) -> Felt252 {
let bytes_part1 = ((coordinates[0] % STWO_PRIME) as u128
+ (((coordinates[1] % STWO_PRIME) as u128) << 36))
.to_le_bytes();
Expand Down
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 @@ -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() {
Expand Down
Loading
Loading