diff --git a/Cargo.lock b/Cargo.lock index 49ef09dd9..fed7ca5df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -743,7 +743,7 @@ dependencies = [ "num-bigint", "num-integer", "num-traits", - "rand 0.9.1", + "rand 0.9.2", "sha2", "smol_str", "starknet-types-core", @@ -1057,7 +1057,7 @@ dependencies = [ "educe 0.5.11", "itertools 0.14.0", "keccak", - "lambdaworks-math 0.11.0", + "lambdaworks-math", "lazy_static", "libc", "libloading", @@ -1069,7 +1069,7 @@ dependencies = [ "num-traits", "pretty_assertions_sorted", "proptest", - "rand 0.9.1", + "rand 0.9.2", "rayon", "rstest", "serde", @@ -1965,8 +1965,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -2325,11 +2327,13 @@ dependencies = [ [[package]] name = "lambdaworks-crypto" -version = "0.10.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" +checksum = "58b1a1c1102a5a7fbbda117b79fb3a01e033459c738a3c1642269603484fd1c1" dependencies = [ - "lambdaworks-math 0.10.0", + "lambdaworks-math", + "rand 0.8.5", + "rand_chacha 0.3.1", "serde", "sha2", "sha3", @@ -2337,20 +2341,14 @@ dependencies = [ [[package]] name = "lambdaworks-math" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1bd2632acbd9957afc5aeec07ad39f078ae38656654043bf16e046fa2730e23" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "lambdaworks-math" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708d148956bcdc21ae5c432b4e20bbaa26fd68d5376a3a6c461f41095abea0ba" +checksum = "018a95aa873eb49896a858dee0d925c33f3978d073c64b08dd4f2c9b35a017c6" dependencies = [ + "getrandom 0.2.16", + "num-bigint", + "num-traits", + "rand 0.8.5", "rayon", "serde", "serde_json", @@ -2938,7 +2936,7 @@ dependencies = [ "bitflags", "lazy_static", "num-traits", - "rand 0.9.1", + "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax 0.8.5", @@ -2987,9 +2985,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -3501,7 +3499,7 @@ dependencies = [ "num-integer", "num-traits", "p256", - "rand 0.9.1", + "rand 0.9.2", "rayon", "sec1", "serde", @@ -3641,18 +3639,19 @@ dependencies = [ [[package]] name = "starknet-types-core" -version = "0.2.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fa3d91e38f091dbc543d33589eb7716bed2a8eb1c20879e484561977832b60a" +checksum = "90d23b1bc014ee4cce40056ab3114bcbcdc2dbc1e845bbfb1f8bd0bab63507d4" dependencies = [ "blake2", "digest", "lambdaworks-crypto", - "lambdaworks-math 0.10.0", + "lambdaworks-math", "lazy_static", "num-bigint", "num-integer", "num-traits", + "rand 0.9.2", "serde", "zeroize", ] diff --git a/Cargo.toml b/Cargo.toml index cb5080611..5a99a0a1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ criterion = "0.5.1" itertools = "0.14.0" k256 = "0.13.4" keccak = "0.1.5" -lambdaworks-math = "0.11.0" +lambdaworks-math = "0.13.0" lazy_static = "1.5" libc = "0.2" libloading = "0.8.6" @@ -78,7 +78,7 @@ sierra-emu = { path = "debug_utils/sierra-emu", version = "0.7.2" } smallvec = "1.13.2" starknet-crypto = "0.8.1" starknet-curve = "0.6.0" -starknet-types-core = "0.2.0" +starknet-types-core = { version = "0.2.3", features = ["hash"]} stats_alloc = "0.1.10" tempfile = "3.15.0" test-case = "3.3" @@ -168,6 +168,7 @@ sha2.workspace = true # Runtime library dependencies. rand.workspace = true starknet-curve.workspace = true +lambdaworks-math.workspace = true [dev-dependencies] cairo-lang-compiler.workspace = true diff --git a/binaries/cairo-native-bin-utils/src/lib.rs b/binaries/cairo-native-bin-utils/src/lib.rs index e72f42052..7b078bcd2 100644 --- a/binaries/cairo-native-bin-utils/src/lib.rs +++ b/binaries/cairo-native-bin-utils/src/lib.rs @@ -166,6 +166,12 @@ fn jitvalue_to_felt(value: &Value) -> Vec { Value::EcState(a, b, c, d) => { vec![*a, *b, *c, *d] } + Value::QM31(a, b, c, d) => vec![ + Felt::from(*a), + Felt::from(*b), + Felt::from(*c), + Felt::from(*d), + ], Value::Secp256K1Point(Secp256k1Point { x, y, diff --git a/corelib.patch b/corelib.patch index 8071ddd9d..1d8c93475 100644 --- a/corelib.patch +++ b/corelib.patch @@ -1044,20 +1044,3 @@ diff --color=auto -crN cairo2/corelib/src/test.cairo cairo2/corelib_1/src/test.c mod hash_test; mod integer_test; mod iter_test; -*************** -*** 24,30 **** - mod option_test; - mod plugins_test; - mod print_test; -! mod qm31_test; - mod range_test; - mod result_test; - mod secp256k1_test; ---- 24,30 ---- - mod option_test; - mod plugins_test; - mod print_test; -! // mod qm31_test; - mod range_test; - mod result_test; - mod secp256k1_test; diff --git a/src/arch.rs b/src/arch.rs index 1d5987922..abfec7ae5 100644 --- a/src/arch.rs +++ b/src/arch.rs @@ -146,6 +146,12 @@ impl AbiArgument for ValueWithInfoWrapper<'_> { x0.to_bytes(buffer, find_dict_drop_override)?; y0.to_bytes(buffer, find_dict_drop_override)?; } + (Value::QM31(a, b, c, d), CoreTypeConcrete::QM31(_)) => { + a.to_bytes(buffer, find_dict_drop_override)?; + b.to_bytes(buffer, find_dict_drop_override)?; + c.to_bytes(buffer, find_dict_drop_override)?; + d.to_bytes(buffer, find_dict_drop_override)?; + } (Value::Enum { tag, value, .. }, CoreTypeConcrete::Enum(info)) => { if self.info.is_memory_allocated(self.registry)? { let abi_ptr = self.value.to_ptr( diff --git a/src/executor.rs b/src/executor.rs index 55ee41edd..edc4a5ea1 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -437,6 +437,21 @@ fn parse_result( registry, true, )?), + CoreTypeConcrete::QM31(_) => match return_ptr { + Some(ptr) => Ok(Value::from_ptr(ptr, type_id, registry, true)?), + None => { + #[cfg(target_arch = "x86_64")] + return Err(Error::ParseAttributeError); + + #[cfg(target_arch = "aarch64")] + Ok(Value::QM31( + u32::try_from(ret_registers[0])?, + u32::try_from(ret_registers[1])?, + u32::try_from(ret_registers[2])?, + u32::try_from(ret_registers[3])?, + )) + } + }, CoreTypeConcrete::Felt252(_) | CoreTypeConcrete::Starknet( StarknetTypeConcrete::ClassHash(_) @@ -679,7 +694,6 @@ fn parse_result( // 2.11.1 CoreTypeConcrete::Blake(_) => native_panic!("blake not yet implemented as results"), // 2.12.0 - CoreTypeConcrete::QM31(_) => native_panic!("qm31 not yet implemented as results"), CoreTypeConcrete::GasReserve(_) => { native_panic!("gas reserve not yet implemented as results") } diff --git a/src/libfuncs.rs b/src/libfuncs.rs index 970e1eb93..b59722e6c 100644 --- a/src/libfuncs.rs +++ b/src/libfuncs.rs @@ -69,6 +69,7 @@ mod mem; mod nullable; mod pedersen; mod poseidon; +mod qm31; mod starknet; mod r#struct; mod uint256; @@ -255,7 +256,9 @@ impl LibfuncBuilder for CoreConcreteLibfunc { metadata, &info.signature.param_signatures, ), - Self::QM31(_) => native_panic!("Implement QM31 libfunc"), + Self::QM31(selector) => self::qm31::build( + context, registry, entry, location, helper, metadata, selector, + ), Self::UnsafePanic(_) => native_panic!("Implement unsafe_panic libfunc"), Self::GasReserve(_) => native_panic!("Implement gas_reserve libfunc"), Self::DummyFunctionCall(_) => native_panic!("Implement dummy_function_call libfunc"), diff --git a/src/libfuncs/qm31.rs b/src/libfuncs/qm31.rs new file mode 100644 index 000000000..758fd3020 --- /dev/null +++ b/src/libfuncs/qm31.rs @@ -0,0 +1,1131 @@ +use crate::{ + error::{Error, Result}, + metadata::runtime_bindings::RuntimeBindingsMeta, + utils::{get_integer_layout, ProgramRegistryExt}, +}; +use crate::{libfuncs::LibfuncHelper, metadata::MetadataStorage}; +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType, CoreTypeConcrete}, + lib_func::SignatureOnlyConcreteLibfunc, + qm31::{QM31BinaryOpConcreteLibfunc, QM31Concrete, QM31ConstConcreteLibfunc}, + ConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use melior::{ + dialect::{ + arith::{self, CmpiPredicate}, + cf, llvm, + }, + helpers::{ArithBlockExt, BuiltinBlockExt, LlvmBlockExt}, + ir::{r#type::IntegerType, Block, BlockLike, Location}, + Context, +}; + +const M31_PRIME: u32 = 0x7fffffff; + +pub fn build<'ctx, 'this>( + context: &'ctx Context, + registry: &ProgramRegistry, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, + metadata: &mut MetadataStorage, + selector: &QM31Concrete, +) -> Result<()> { + match selector { + QM31Concrete::BinaryOperation(info) => { + build_binary_op(context, registry, entry, location, helper, metadata, info) + } + QM31Concrete::Const(info) => { + build_const(context, registry, entry, location, helper, metadata, info) + } + QM31Concrete::IsZero(info) => { + build_is_zero(context, registry, entry, location, helper, metadata, info) + } + QM31Concrete::Pack(info) => { + build_pack(context, registry, entry, location, helper, metadata, info) + } + QM31Concrete::Unpack(info) => { + build_unpack(context, registry, entry, location, helper, metadata, info) + } + QM31Concrete::FromM31(info) => { + build_from_m31(context, registry, entry, location, helper, metadata, info) + } + } +} + +/// Generate MLIR operations for the `qm31_const` libfunc. +/// +/// Receives 4 const m31 and returns a qm31. +/// +/// # Constraints +/// +/// m31 are always between 0 and 2**31 - 2 (inclusive) +/// +/// # Cairo Signature +/// +/// ```cairo +/// fn qm31_const< +/// const W0: m31, const W1: m31, const W2: m31, const W3: m31, +/// >() -> qm31 nopanic; +/// ``` +pub fn build_const<'ctx, 'this>( + context: &'ctx Context, + registry: &ProgramRegistry, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, + metadata: &mut MetadataStorage, + info: &QM31ConstConcreteLibfunc, +) -> Result<()> { + let m31_ty = IntegerType::new(context, 31).into(); + let qm31_ty = registry.build_type( + context, + helper, + metadata, + &info.branch_signatures()[0].vars[0].ty, + )?; + + let m31_0 = entry.const_int_from_type(context, location, info.w0, m31_ty)?; + let m31_1 = entry.const_int_from_type(context, location, info.w1, m31_ty)?; + let m31_2 = entry.const_int_from_type(context, location, info.w2, m31_ty)?; + let m31_3 = entry.const_int_from_type(context, location, info.w3, m31_ty)?; + + let qm31 = entry.append_op_result(llvm::undef(qm31_ty, location))?; + let qm31 = entry.insert_values(context, location, qm31, &[m31_0, m31_1, m31_2, m31_3])?; + + helper.br(entry, 0, &[qm31], location) +} + +/// Generate MLIR operations for the `qm31_is_zero` libfunc. +/// +/// Receives a qm31 and returns a Some(qm31) if the argument is not 0, +/// otherwise a None. +/// +/// # Cairo Signature +/// +/// ```cairo +/// fn qm31_is_zero(a: qm31) -> core::internal::OptionRev> nopanic; +/// ``` +pub fn build_is_zero<'ctx, 'this>( + context: &'ctx Context, + _registry: &ProgramRegistry, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, + _metadata: &mut MetadataStorage, + _info: &SignatureOnlyConcreteLibfunc, +) -> Result<()> { + let qm31 = entry.arg(0)?; + let m31_ty = IntegerType::new(context, 31).into(); + + let m31_0 = entry.extract_value(context, location, qm31, m31_ty, 0)?; + let m31_1 = entry.extract_value(context, location, qm31, m31_ty, 1)?; + let m31_2 = entry.extract_value(context, location, qm31, m31_ty, 2)?; + let m31_3 = entry.extract_value(context, location, qm31, m31_ty, 3)?; + + // Check that every limb is equal to 0: + // (m31_0 | m31_1 | m31_2 | m31_3) == 0 + let cond = entry.append_op_result(arith::ori(m31_0, m31_1, location))?; + let cond = entry.append_op_result(arith::ori(cond, m31_2, location))?; + let cond = entry.append_op_result(arith::ori(cond, m31_3, location))?; + let k0 = entry.const_int_from_type(context, location, 0, m31_ty)?; + let cond = + entry.append_op_result(arith::cmpi(context, CmpiPredicate::Eq, k0, cond, location))?; + + helper.cond_br( + context, + entry, + cond, + [0, 1], + [&[], &[entry.arg(0)?]], + location, + ) +} + +/// Generate MLIR operations for the `qm31_pack` libfunc. +/// +/// Receives four m31 and packs them into a qm31. +/// +/// # Constraints +/// +/// m31 are always between 0 and 2**31 - 2 (inclusive) +/// +/// # Cairo Signature +/// +/// ```cairo +/// fn qm31_pack(w0: m31, w1: m31, w2: m31, w3: m31) -> qm31 nopanic; +/// ``` +pub fn build_pack<'ctx, 'this>( + context: &'ctx Context, + registry: &ProgramRegistry, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, + metadata: &mut MetadataStorage, + info: &SignatureOnlyConcreteLibfunc, +) -> Result<()> { + let m31_0 = entry.arg(0)?; + let m31_1 = entry.arg(1)?; + let m31_2 = entry.arg(2)?; + let m31_3 = entry.arg(3)?; + + let qm31_ty = registry.build_type( + context, + helper, + metadata, + &info.branch_signatures()[0].vars[0].ty, + )?; + + let qm31 = entry.append_op_result(llvm::undef(qm31_ty, location))?; + let qm31 = entry.insert_values(context, location, qm31, &[m31_0, m31_1, m31_2, m31_3])?; + + helper.br(entry, 0, &[qm31], location) +} + +/// Generate MLIR operations for the `qm31_unpack` libfunc. +/// +/// Receives a qm31 and unpacks it, returning an array with the +/// four m31. +/// +/// # Constraints +/// +/// m31 are always between 0 and 2**31 - 2 (inclusive) +/// +/// # Cairo Signature +/// +/// ```cairo +/// fn qm31_unpack(a: qm31) -> (m31, m31, m31, m31) implicits(crate::RangeCheck) nopanic; +/// ``` +pub fn build_unpack<'ctx, 'this>( + context: &'ctx Context, + _registry: &ProgramRegistry, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, + _metadata: &mut MetadataStorage, + _info: &SignatureOnlyConcreteLibfunc, +) -> Result<()> { + let range_check = + super::increment_builtin_counter_by(context, entry, location, entry.arg(0)?, 5)?; + let m31_ty = IntegerType::new(context, 31); + let qm31 = entry.arg(1)?; + + let m31_0 = entry.extract_value(context, location, qm31, m31_ty.into(), 0)?; + let m31_1 = entry.extract_value(context, location, qm31, m31_ty.into(), 1)?; + let m31_2 = entry.extract_value(context, location, qm31, m31_ty.into(), 2)?; + let m31_3 = entry.extract_value(context, location, qm31, m31_ty.into(), 3)?; + + helper.br( + entry, + 0, + &[range_check, m31_0, m31_1, m31_2, m31_3], + location, + ) +} + +/// Generate MLIR operations for the `qm31_from_m31` libfunc. +/// +/// Receives a m31 and returns a qm31 in which its first coeffiecient +/// has the value of the input and the other ones are 0. +/// +/// # Constraints +/// +/// m31 are always between 0 and 2**31 - 2 (inclusive) +/// +/// # Cairo Signature +/// +/// ```cairo +/// fn qm31_from_m31(a: m31) -> qm31 nopanic; +/// ``` +pub fn build_from_m31<'ctx, 'this>( + context: &'ctx Context, + registry: &ProgramRegistry, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, + metadata: &mut MetadataStorage, + info: &SignatureOnlyConcreteLibfunc, +) -> Result<()> { + let m31 = entry.arg(0)?; + + let m31_ty = IntegerType::new(context, 31).into(); + let qm31_ty = registry.build_type( + context, + helper, + metadata, + &info.branch_signatures()[0].vars[0].ty, + )?; + let k0 = entry.const_int_from_type(context, location, 0, m31_ty)?; + + let qm31 = entry.append_op_result(llvm::undef(qm31_ty, location))?; + let qm31 = entry.insert_values(context, location, qm31, &[m31, k0, k0, k0])?; + + helper.br(entry, 0, &[qm31], location) +} + +fn m31_add<'ctx, 'this>( + context: &'ctx Context, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, +) -> Result<()> { + let lhs_value = entry.arg(0)?; + let rhs_value = entry.arg(1)?; + + let lhs_value = entry.extui(lhs_value, IntegerType::new(context, 32).into(), location)?; + let rhs_value = entry.extui(rhs_value, IntegerType::new(context, 32).into(), location)?; + + let res = entry.append_op_result(arith::addi(lhs_value, rhs_value, location))?; + let prime = entry.const_int(context, location, M31_PRIME, 32)?; + let res_mod = entry.append_op_result(arith::subi(res, prime, location))?; + let is_out_of_range = entry.cmpi(context, CmpiPredicate::Uge, res, prime, location)?; + + let res = entry.append_op_result(arith::select(is_out_of_range, res_mod, res, location))?; + + let res = entry.trunci(res, IntegerType::new(context, 31).into(), location)?; + + helper.br(entry, 0, &[res], location) +} + +fn m31_sub<'ctx, 'this>( + context: &'ctx Context, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, +) -> Result<()> { + let lhs_value = entry.arg(0)?; + let rhs_value = entry.arg(1)?; + + let res = entry.append_op_result(arith::subi(lhs_value, rhs_value, location))?; + let prime = entry.const_int(context, location, M31_PRIME, 31)?; + let res_mod = entry.append_op_result(arith::addi(res, prime, location))?; + let is_out_of_range = + entry.cmpi(context, CmpiPredicate::Ult, lhs_value, rhs_value, location)?; + + let res = entry.append_op_result(arith::select(is_out_of_range, res_mod, res, location))?; + helper.br(entry, 0, &[res], location) +} + +fn m31_mul<'ctx, 'this>( + context: &'ctx Context, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, +) -> Result<()> { + let lhs_value = entry.arg(0)?; + let rhs_value = entry.arg(1)?; + + let lhs_value = entry.extui(lhs_value, IntegerType::new(context, 64).into(), location)?; + let rhs_value = entry.extui(rhs_value, IntegerType::new(context, 64).into(), location)?; + let res = entry.muli(lhs_value, rhs_value, location)?; + + let prime = entry.const_int(context, location, M31_PRIME, 64)?; + let res_mod = entry.append_op_result(arith::remui(res, prime, location))?; + let is_out_of_range = entry.cmpi(context, CmpiPredicate::Uge, res, prime, location)?; + + let res = entry.append_op_result(arith::select(is_out_of_range, res_mod, res, location))?; + let res = entry.trunci(res, IntegerType::new(context, 31).into(), location)?; + + helper.br(entry, 0, &[res], location) +} + +fn m31_div<'ctx, 'this>( + context: &'ctx Context, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, +) -> Result<()> { + let lhs_value = entry.arg(0)?; + let rhs_value = entry.arg(1)?; + + let i31 = IntegerType::new(context, 31).into(); + let i64 = IntegerType::new(context, 64).into(); + + let start_block = helper.append_block(Block::new(&[(i31, location)])); + let loop_block = helper.append_block(Block::new(&[ + (i31, location), + (i31, location), + (i31, location), + (i31, location), + ])); + let negative_check_block = helper.append_block(Block::new(&[])); + // Block containing final result + let inverse_result_block = helper.append_block(Block::new(&[(i31, location)])); + // Egcd works by calculating a series of remainders, each the remainder of dividing the previous two + // For the initial setup, r0 = PRIME, r1 = a + // This order is chosen because if we reverse them, then the first iteration will just swap them + let prev_remainder = start_block.const_int_from_type(context, location, M31_PRIME, i31)?; + let remainder = start_block.arg(0)?; + // Similarly we'll calculate another series which starts 0,1,... and from which we will retrieve the modular inverse of a + let prev_inverse = start_block.const_int_from_type(context, location, 0, i31)?; + let inverse = start_block.const_int_from_type(context, location, 1, i31)?; + start_block.append_operation(cf::br( + loop_block, + &[prev_remainder, remainder, prev_inverse, inverse], + location, + )); + + //---Loop body--- + // Arguments are rem_(i-1), rem, inv_(i-1), inv + let prev_remainder = loop_block.arg(0)?; + let remainder = loop_block.arg(1)?; + let prev_inverse = loop_block.arg(2)?; + let inverse = loop_block.arg(3)?; + + // First calculate q = rem_(i-1)/rem_i, rounded down + let quotient = + loop_block.append_op_result(arith::divui(prev_remainder, remainder, location))?; + // Then r_(i+1) = r_(i-1) - q * r_i, and inv_(i+1) = inv_(i-1) - q * inv_i + let rem_times_quo = loop_block.muli(remainder, quotient, location)?; + let inv_times_quo = loop_block.muli(inverse, quotient, location)?; + let next_remainder = + loop_block.append_op_result(arith::subi(prev_remainder, rem_times_quo, location))?; + let next_inverse = + loop_block.append_op_result(arith::subi(prev_inverse, inv_times_quo, location))?; + + // If r_(i+1) is 0, then inv_i is the inverse + let zero = loop_block.const_int_from_type(context, location, 0, i31)?; + let next_remainder_eq_zero = + loop_block.cmpi(context, CmpiPredicate::Eq, next_remainder, zero, location)?; + loop_block.append_operation(cf::cond_br( + context, + next_remainder_eq_zero, + negative_check_block, + loop_block, + &[], + &[remainder, next_remainder, inverse, next_inverse], + location, + )); + + // egcd sometimes returns a negative number for the inverse, + // in such cases we must simply wrap it around back into [0, PRIME) + // this suffices because |inv_i| <= divfloor(PRIME,2) + let zero = negative_check_block.const_int_from_type(context, location, 0, i31)?; + let is_negative = negative_check_block + .append_operation(arith::cmpi( + context, + CmpiPredicate::Slt, + inverse, + zero, + location, + )) + .result(0)? + .into(); + // if the inverse is < 0, add PRIME + let prime = negative_check_block.const_int_from_type(context, location, M31_PRIME, i31)?; + let wrapped_inverse = negative_check_block.addi(inverse, prime, location)?; + let inverse = negative_check_block.append_op_result(arith::select( + is_negative, + wrapped_inverse, + inverse, + location, + ))?; + negative_check_block.append_operation(cf::br(inverse_result_block, &[inverse], location)); + + // Div Logic Start + // Fetch operands + let lhs_value = entry.extui(lhs_value, i64, location)?; + // Calculate inverse of rhs, callling the inverse implementation's starting block + entry.append_operation(cf::br(start_block, &[rhs_value], location)); + // Fetch the inverse result from the result block + let inverse = inverse_result_block.arg(0)?; + let inverse = inverse_result_block.extui(inverse, i64, location)?; + // Peform lhs * (1/ rhs) + let result = inverse_result_block.muli(lhs_value, inverse, location)?; + // Apply modulo and convert result to m31 + let prime = inverse_result_block.extui(prime, i64, location)?; + let result_mod = + inverse_result_block.append_op_result(arith::remui(result, prime, location))?; + let is_out_of_range = + inverse_result_block.cmpi(context, CmpiPredicate::Uge, result, prime, location)?; + + let result = inverse_result_block.append_op_result(arith::select( + is_out_of_range, + result_mod, + result, + location, + ))?; + let result = inverse_result_block.trunci(result, i31, location)?; + + helper.br(inverse_result_block, 0, &[result], location) +} + +/// Generate MLIR operations for the QM31 and M31 binary operations libfuncs. +/// +/// Depending on the type of the parameters, it chooses which type of representation +/// it will manage (QM31 or M31). It either receives 2 qm31 or 2 m31 (which are represented +/// as bounded ints) +/// +/// # Cairo Signature +/// ```cairo +/// // qm31 +/// fn qm31_add(a: qm31, b: qm31) -> qm31 nopanic; +/// fn qm31_sub(a: qm31, b: qm31) -> qm31 nopanic; +/// fn qm31_mul(a: qm31, b: qm31) -> qm31 nopanic; +/// fn qm31_div(a: qm31, b: NonZero) -> qm31 nopanic; +/// +/// // m31 +/// extern fn m31_add(a: m31, b: m31) -> m31 nopanic; +/// extern fn m31_sub(a: m31, b: m31) -> m31 nopanic; +/// extern fn m31_mul(a: m31, b: m31) -> m31 nopanic; +/// extern fn m31_div(a: m31, b: NonZero) -> m31 nopanic; +/// ``` +pub fn build_binary_op<'ctx, 'this>( + context: &'ctx Context, + registry: &ProgramRegistry, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, + metadata: &mut MetadataStorage, + info: &QM31BinaryOpConcreteLibfunc, +) -> Result<()> { + // If the parameter is a bounded int, then we need to generate the operations + // for the m31 + let type_concrete = registry.get_type(&info.param_signatures()[0].ty)?; + if let CoreTypeConcrete::BoundedInt(_) = type_concrete { + match info.operator { + cairo_lang_sierra::extensions::qm31::QM31BinaryOperator::Add => { + return m31_add(context, entry, location, helper); + } + cairo_lang_sierra::extensions::qm31::QM31BinaryOperator::Sub => { + return m31_sub(context, entry, location, helper); + } + cairo_lang_sierra::extensions::qm31::QM31BinaryOperator::Mul => { + return m31_mul(context, entry, location, helper); + } + cairo_lang_sierra::extensions::qm31::QM31BinaryOperator::Div => { + return m31_div(context, entry, location, helper); + } + } + } + let qm31_ty = registry.build_type(context, helper, metadata, &info.param_signatures()[0].ty)?; + + let lhs = entry.arg(0)?; + let rhs = entry.arg(1)?; + + let lhs_ptr = entry.alloca1(context, location, qm31_ty, get_integer_layout(31).align())?; + let rhs_ptr = entry.alloca1(context, location, qm31_ty, get_integer_layout(31).align())?; + + entry.store(context, location, lhs_ptr, lhs)?; + entry.store(context, location, rhs_ptr, rhs)?; + + let result = metadata + .get_mut::() + .ok_or(Error::MissingMetadata)? + .libfunc_qm31_bin_op( + context, + helper, + entry, + lhs_ptr, + rhs_ptr, + info.operator, + location, + )?; + + helper.br(entry, 0, &[result], location) +} + +#[cfg(test)] +mod test { + use ark_ff::Zero; + use cairo_lang_sierra::extensions::utils::Range; + use cairo_vm::Felt252; + use num_bigint::BigInt; + + use crate::{ + jit_enum, jit_struct, libfuncs::qm31::M31_PRIME, load_cairo, + runtime::to_representative_coefficients, utils::testing::run_program, Value, + }; + + impl From<&starknet_types_core::qm31::QM31> for Value { + fn from(qm31: &starknet_types_core::qm31::QM31) -> Self { + let coefficients = to_representative_coefficients(qm31.clone()); + Value::QM31( + coefficients[0], + coefficients[1], + coefficients[2], + coefficients[3], + ) + } + } + + #[test] + fn run_unpack() { + let program = load_cairo! { + use core::qm31::{QM31Trait, m31, qm31}; + + fn run_test_1() -> [m31;4] { + let qm31 = QM31Trait::new(1, 2, 3, 4); + let unpacked_qm31 = qm31.unpack(); + + unpacked_qm31 + } + + fn run_test_2() -> [m31;4] { + let qm31 = QM31Trait::new(0x544b2fba, 0x673cff77, 0x60713d44, 0x499602d2); + let unpacked_qm31 = qm31.unpack(); + + unpacked_qm31 + } + }; + + let result = run_program(&program, "run_test_1", &[]).return_value; + let m31_range = Range::closed(0, BigInt::from(2147483646)); + let Value::Struct { fields, .. } = result else { + panic!("Expected a Value::Struct()"); + }; + assert_eq!( + fields, + vec![ + Value::BoundedInt { + value: Felt252::from(1), + range: m31_range.clone() + }, + Value::BoundedInt { + value: Felt252::from(2), + range: m31_range.clone() + }, + Value::BoundedInt { + value: Felt252::from(3), + range: m31_range.clone() + }, + Value::BoundedInt { + value: Felt252::from(4), + range: m31_range.clone() + }, + ] + ); + + let result = run_program(&program, "run_test_2", &[]).return_value; + let Value::Struct { fields, .. } = result else { + panic!("Expected a Value::Struct()"); + }; + assert_eq!( + fields, + vec![ + Value::BoundedInt { + value: Felt252::from(0x544b2fba), + range: m31_range.clone() + }, + Value::BoundedInt { + value: Felt252::from(0x673cff77), + range: m31_range.clone() + }, + Value::BoundedInt { + value: Felt252::from(0x60713d44), + range: m31_range.clone() + }, + Value::BoundedInt { + value: Felt252::from(0x499602d2), + range: m31_range.clone() + }, + ] + ); + } + + #[test] + fn run_pack() { + let program = load_cairo! { + use core::qm31::{QM31Trait, qm31}; + + fn run_test() -> qm31 { + let qm31 = QM31Trait::new(1, 2, 3, 4); + qm31 + } + + fn run_test_large_coefficients() -> qm31 { + let qm31 = QM31Trait::new(0x544b2fba, 0x673cff77, 0x60713d44, 0x499602d2); + qm31 + } + }; + // With small coefficients + let result = run_program(&program, "run_test", &[]).return_value; + assert_eq!(result, Value::QM31(1, 2, 3, 4)); + + // With big coefficients + let result = run_program(&program, "run_test_large_coefficients", &[]).return_value; + let qm31_expected = starknet_types_core::qm31::QM31::from_coefficients( + 0x544b2fba, 0x673cff77, 0x60713d44, 0x499602d2, + ); + let expected_coefficients = qm31_expected.to_coefficients(); + assert_eq!( + result, + Value::QM31( + expected_coefficients.0, + expected_coefficients.1, + expected_coefficients.2, + expected_coefficients.3 + ) + ); + } + + #[test] + fn run_const() { + let program = load_cairo! { + use core::qm31::{qm31_const, qm31}; + + fn run_test() -> qm31 { + let qm31 = qm31_const::<1, 2, 3, 4>(); + qm31 + } + + fn run_test_large_coefficients() -> qm31 { + let qm31 = qm31_const::<0x544b2fba, 0x673cff77, 0x60713d44, 0x499602d2>(); + qm31 + } + }; + + let result = run_program(&program, "run_test", &[]).return_value; + assert_eq!(result, Value::QM31(1, 2, 3, 4)); + + let result = run_program(&program, "run_test_large_coefficients", &[]).return_value; + assert_eq!( + result, + Value::QM31(0x544b2fba, 0x673cff77, 0x60713d44, 0x499602d2) + ); + } + + #[test] + fn run_is_zero() { + let program = load_cairo! { + use core::qm31::{QM31Trait, qm31, qm31_is_zero}; + use core::internal::OptionRev; + + fn run_test(input: qm31) -> OptionRev> { + qm31_is_zero(input) + } + + fn run_test_edge_case() -> OptionRev> { + let lhs = QM31Trait::new(0x7ffffffe, 0x7ffffffe, 0x7ffffffe, 0x7ffffffe); + let rhs = QM31Trait::new(1, 1, 1, 1); + let qm31 = lhs + rhs; + qm31_is_zero(qm31) + } + }; + + let result = run_program(&program, "run_test", &[Value::QM31(0, 0, 0, 0)]).return_value; + assert_eq!(result, jit_enum!(0, jit_struct!())); + + let result = run_program(&program, "run_test", &[Value::QM31(0, 0, 1, 0)]).return_value; + assert_eq!(result, jit_enum!(1, Value::QM31(0, 0, 1, 0))); + + let result = run_program( + &program, + "run_test", + &[Value::QM31(0x544b2fba, 0x673cff77, 0x60713d44, 0x499602d2)], + ) + .return_value; + assert_eq!( + result, + jit_enum!( + 1, + Value::QM31(0x544b2fba, 0x673cff77, 0x60713d44, 0x499602d2) + ) + ); + + let result = run_program(&program, "run_test_edge_case", &[]).return_value; + assert_eq!(result, jit_enum!(0, jit_struct!())); + } + + #[test] + fn run_qm31_add() { + let program = load_cairo! { + use core::qm31::{QM31Trait, qm31}; + + fn run_test(lhs: qm31, rhs: qm31) -> qm31 { + lhs + rhs + } + }; + + let a = starknet_types_core::qm31::QM31::from_coefficients( + 0x544b2fba, 0x673cff77, 0x60713d44, 0x499602d2, + ); + let b = starknet_types_core::qm31::QM31::from_coefficients( + 0x499602d2, 0x544b2fba, 0x673cff77, 0x60713d44, + ); + let c = starknet_types_core::qm31::QM31::from_coefficients( + 0x1de1328d, 0x3b882f32, 0x47ae3cbc, 0x2a074017, + ); + let d = starknet_types_core::qm31::QM31::from_coefficients( + 0x7ffffffe, 0x7ffffffe, 0x7ffffffe, 0x7ffffffe, + ); + let e = starknet_types_core::qm31::QM31::from_coefficients(1, 1, 1, 1); + + let result = + run_program(&program, "run_test", &[Value::from(&a), Value::from(&b)]).return_value; + let expected_qm31 = a.clone() + b.clone(); + assert_eq!(result, Value::from(&expected_qm31)); + + let result = + run_program(&program, "run_test", &[Value::from(&b), Value::from(&c)]).return_value; + let expected_qm31 = b + c.clone(); + assert_eq!(result, Value::from(&expected_qm31)); + + let result = + run_program(&program, "run_test", &[Value::from(&a), Value::from(&c)]).return_value; + let expected_qm31 = a + c; + assert_eq!(result, Value::from(&expected_qm31)); + + let result = + run_program(&program, "run_test", &[Value::from(&d), Value::from(&e)]).return_value; + assert_eq!(result, Value::QM31(0, 0, 0, 0)); + } + + #[test] + fn run_qm31_sub() { + let program = load_cairo! { + use core::qm31::{QM31Trait, qm31}; + + fn run_test(lhs: qm31, rhs: qm31) -> qm31 { + lhs - rhs + } + }; + + let a = starknet_types_core::qm31::QM31::from_coefficients( + 0x544b2fba, 0x673cff77, 0x60713d44, 0x499602d2, + ); + let b = starknet_types_core::qm31::QM31::from_coefficients( + 0x499602d2, 0x544b2fba, 0x673cff77, 0x60713d44, + ); + let c = starknet_types_core::qm31::QM31::from_coefficients( + 0x1de1328d, 0x3b882f32, 0x47ae3cbc, 0x2a074017, + ); + + let result = + run_program(&program, "run_test", &[Value::from(&c), Value::from(&a)]).return_value; + let expected_qm31 = c.clone() - a.clone(); + assert_eq!(result, Value::from(&expected_qm31)); + + let result = + run_program(&program, "run_test", &[Value::from(&a), Value::from(&b)]).return_value; + let expected_qm31 = a - b.clone(); + assert_eq!(result, Value::from(&expected_qm31)); + + let result = + run_program(&program, "run_test", &[Value::from(&b), Value::from(&c)]).return_value; + let expected_qm31 = b - c; + assert_eq!(result, Value::from(&expected_qm31)); + } + + #[test] + fn run_qm31_mul() { + let program = load_cairo! { + use core::qm31::{QM31Trait, qm31, m31}; + + fn run_test(lhs: qm31, rhs: qm31) -> qm31 { + lhs * rhs + } + }; + + let a = starknet_types_core::qm31::QM31::from_coefficients( + 0x544b2fba, 0x673cff77, 0x60713d44, 0x499602d2, + ); + let b = starknet_types_core::qm31::QM31::from_coefficients( + 0x499602d2, 0x544b2fba, 0x673cff77, 0x60713d44, + ); + let c = starknet_types_core::qm31::QM31::from_coefficients( + 0x1de1328d, 0x3b882f32, 0x47ae3cbc, 0x2a074017, + ); + let d = starknet_types_core::qm31::QM31::zero(); + + let result = + run_program(&program, "run_test", &[Value::from(&a), Value::from(&b)]).return_value; + let expected_qm31 = a.clone() * b.clone(); + assert_eq!(result, Value::from(&expected_qm31)); + + let result = + run_program(&program, "run_test", &[Value::from(&a), Value::from(&c)]).return_value; + let expected_qm31 = a.clone() * c.clone(); + assert_eq!(result, Value::from(&expected_qm31)); + + let result = + run_program(&program, "run_test", &[Value::from(&b), Value::from(&c)]).return_value; + let expected_qm31 = b.clone() * c; + assert_eq!(result, Value::from(&expected_qm31)); + + let result = + run_program(&program, "run_test", &[Value::from(&d), Value::from(&b)]).return_value; + let expected_qm31 = d * b; + assert_eq!(result, Value::from(&expected_qm31)); + } + + #[test] + fn run_qm31_div() { + let program = load_cairo! { + use core::qm31::{QM31Trait, qm31, m31}; + + fn run_test(lhs: qm31, rhs: qm31) -> qm31 { + lhs / rhs + } + }; + + let a = starknet_types_core::qm31::QM31::from_coefficients( + 0x544b2fba, 0x673cff77, 0x60713d44, 0x499602d2, + ); + let b = starknet_types_core::qm31::QM31::from_coefficients( + 0x499602d2, 0x544b2fba, 0x673cff77, 0x60713d44, + ); + let c = starknet_types_core::qm31::QM31::from_coefficients( + 0x1de1328d, 0x3b882f32, 0x47ae3cbc, 0x2a074017, + ); + let d = starknet_types_core::qm31::QM31::from_coefficients(0, 0, 0, 0); + + let result = + run_program(&program, "run_test", &[Value::from(&c), Value::from(&a)]).return_value; + let expected_qm31 = (c.clone() / a.clone()).unwrap(); + assert_eq!( + result, + jit_enum!(0, jit_struct!(Value::from(&expected_qm31))) + ); + + let result = + run_program(&program, "run_test", &[Value::from(&a), Value::from(&b)]).return_value; + let expected_qm31 = (a.clone() / b.clone()).unwrap(); + assert_eq!( + result, + jit_enum!(0, jit_struct!(Value::from(&expected_qm31))) + ); + + let result = + run_program(&program, "run_test", &[Value::from(&b), Value::from(&c)]).return_value; + let expected_qm31 = (b.clone() / c).unwrap(); + assert_eq!( + result, + jit_enum!(0, jit_struct!(Value::from(&expected_qm31))) + ); + + let result = + run_program(&program, "run_test", &[Value::from(&b), Value::from(&d)]).return_value; + if let Value::Enum { tag, .. } = result { + assert_eq!(tag, 1); + } else { + panic!("Expected a Value::Enum()"); + } + } + + #[test] + fn run_from_m31() { + let program = load_cairo! { + use core::qm31::{QM31Trait, qm31, m31, qm31_from_m31}; + + fn run_test_with_0() -> qm31 { + qm31_from_m31(0) + } + + fn run_test_with_1() -> qm31 { + qm31_from_m31(1) + } + + fn run_test_with_big_coefficient() -> qm31 { + qm31_from_m31(0x60713d44) + } + }; + + let result = run_program(&program, "run_test_with_0", &[]).return_value; + assert_eq!(result, Value::QM31(0, 0, 0, 0)); + + let result = run_program(&program, "run_test_with_1", &[]).return_value; + assert_eq!(result, Value::QM31(1, 0, 0, 0)); + + let result = run_program(&program, "run_test_with_big_coefficient", &[]).return_value; + assert_eq!(result, Value::QM31(0x60713d44, 0, 0, 0)); + } + + #[test] + fn run_m31_add() { + // TODO: Refactor cairo functions to receive m31 as parameters so we don't need different ones + // to test different cases and we can unify them into one. This can be done when issue #1217 gets closed. + let program = load_cairo! { + use core::qm31::m31_ops; + use core::qm31::m31; + + fn run_test_1() -> m31 { + m31_ops::add(1, 1) + } + + fn run_test_2() -> m31 { + m31_ops::add(0x567effa3, 0x5ffeb970) + } + + fn run_test_3() -> m31 { + m31_ops::add(0x7ffffffe, 1) + } + }; + let expected_range = Range { + lower: 0.into(), + upper: M31_PRIME.into(), + }; + let result = run_program(&program, "run_test_1", &[]).return_value; + assert_eq!( + result, + Value::BoundedInt { + value: Felt252::from(2), + range: expected_range.clone() + } + ); + + let result = run_program(&program, "run_test_2", &[]).return_value; + assert_eq!( + result, + Value::BoundedInt { + value: Felt252::from(0x367db914), + range: expected_range.clone() + } + ); + + let result = run_program(&program, "run_test_3", &[]).return_value; + assert_eq!( + result, + Value::BoundedInt { + value: Felt252::from(0), + range: expected_range.clone() + } + ); + } + + #[test] + fn run_m31_sub() { + // TODO: Refactor cairo functions to receive m31 as parameters so we don't need different ones + // to test different cases and we can unify them into one. This can be done when issue #1217 gets closed. + let program = load_cairo! { + use core::qm31::m31_ops; + use core::qm31::m31; + + fn run_test_1() -> m31 { + m31_ops::sub(2, 1) + } + + fn run_test_2() -> m31 { + m31_ops::sub(0x567effa3, 0x567effa9) + } + + fn run_test_3() -> m31 { + m31_ops::sub(0, 1) + } + }; + let expected_range = Range { + lower: 0.into(), + upper: M31_PRIME.into(), + }; + let result = run_program(&program, "run_test_1", &[]).return_value; + assert_eq!( + result, + Value::BoundedInt { + value: Felt252::from(1), + range: expected_range.clone() + } + ); + + let result = run_program(&program, "run_test_2", &[]).return_value; + assert_eq!( + result, + Value::BoundedInt { + value: Felt252::from(0x7ffffff9), + range: expected_range.clone() + } + ); + + let result = run_program(&program, "run_test_3", &[]).return_value; + assert_eq!( + result, + Value::BoundedInt { + value: Felt252::from(0x7ffffffe), + range: expected_range.clone() + } + ); + } + + #[test] + fn run_m31_mul() { + // TODO: Refactor cairo functions to receive m31 as parameters so we don't need different ones + // to test different cases and we can unify them into one. This can be done when issue #1217 gets closed. + let program = load_cairo! { + use core::qm31::m31_ops; + use core::qm31::m31; + + fn run_test_1() -> m31 { + m31_ops::mul(5, 5) + } + + fn run_test_2() -> m31 { + m31_ops::mul(0x567effa3, 0x567effa9) + } + + fn run_test_3() -> m31 { + m31_ops::mul(0x7ffffffe, 2) + } + }; + let expected_range = Range { + lower: 0.into(), + upper: M31_PRIME.into(), + }; + let result = run_program(&program, "run_test_1", &[]).return_value; + assert_eq!( + result, + Value::BoundedInt { + value: Felt252::from(25), + range: expected_range.clone() + } + ); + + let result = run_program(&program, "run_test_2", &[]).return_value; + assert_eq!( + result, + Value::BoundedInt { + value: Felt252::from(0x69274523), + range: expected_range.clone() + } + ); + + let result = run_program(&program, "run_test_3", &[]).return_value; + assert_eq!( + result, + Value::BoundedInt { + value: Felt252::from(0x7ffffffd), + range: expected_range.clone() + } + ); + } + + #[test] + fn run_m31_div() { + // TODO: Refactor cairo functions to receive m31 as parameters so we don't need different ones + // to test different cases and we can unify them into one. This can be done when issue #1217 gets closed. + let program = load_cairo! { + use core::qm31::m31_ops; + use core::qm31::m31; + + fn run_test_1() -> m31 { + m31_ops::div(25, 5) + } + + fn run_test_2() -> m31 { + m31_ops::div(0x567effa3, 0x567effa9) + } + }; + let expected_range = Range { + lower: 0.into(), + upper: M31_PRIME.into(), + }; + let result = run_program(&program, "run_test_1", &[]).return_value; + assert_eq!( + result, + Value::BoundedInt { + value: Felt252::from(5), + range: expected_range.clone() + } + ); + + let result = run_program(&program, "run_test_2", &[]).return_value; + assert_eq!( + result, + Value::BoundedInt { + value: Felt252::from(0x5138acb), + range: expected_range.clone() + } + ); + } +} diff --git a/src/metadata/runtime_bindings.rs b/src/metadata/runtime_bindings.rs index aeefd674b..2c5499980 100644 --- a/src/metadata/runtime_bindings.rs +++ b/src/metadata/runtime_bindings.rs @@ -6,7 +6,9 @@ use crate::{ error::{Error, Result}, libfuncs::LibfuncHelper, + utils::get_integer_layout, }; +use cairo_lang_sierra::extensions::qm31::QM31BinaryOperator; use itertools::Itertools; use melior::{ dialect::{ @@ -48,6 +50,10 @@ enum RuntimeBinding { DebugPrint, ExtendedEuclideanAlgorithm, CircuitArithOperation, + QM31Add, + QM31Sub, + QM31Mul, + QM31Div, #[cfg(feature = "with-cheatcode")] VtableCheatcode, } @@ -76,6 +82,10 @@ impl RuntimeBinding { "cairo_native__extended_euclidean_algorithm" } RuntimeBinding::CircuitArithOperation => "cairo_native__circuit_arith_operation", + RuntimeBinding::QM31Add => "cairo_native__libfunc__qm31__qm31_add", + RuntimeBinding::QM31Sub => "cairo_native__libfunc__qm31__qm31_sub", + RuntimeBinding::QM31Mul => "cairo_native__libfunc__qm31__qm31_mul", + RuntimeBinding::QM31Div => "cairo_native__libfunc__qm31__qm31_div", #[cfg(feature = "with-cheatcode")] RuntimeBinding::VtableCheatcode => "cairo_native__vtable_cheatcode", } @@ -124,6 +134,18 @@ impl RuntimeBinding { RuntimeBinding::GetCostsBuiltin => { crate::runtime::cairo_native__get_costs_builtin as *const () } + RuntimeBinding::QM31Add => { + crate::runtime::cairo_native__libfunc__qm31__qm31_add as *const () + } + RuntimeBinding::QM31Sub => { + crate::runtime::cairo_native__libfunc__qm31__qm31_sub as *const () + } + RuntimeBinding::QM31Mul => { + crate::runtime::cairo_native__libfunc__qm31__qm31_mul as *const () + } + RuntimeBinding::QM31Div => { + crate::runtime::cairo_native__libfunc__qm31__qm31_div as *const () + } RuntimeBinding::ExtendedEuclideanAlgorithm => return None, RuntimeBinding::CircuitArithOperation => return None, #[cfg(feature = "with-cheatcode")] @@ -547,6 +569,57 @@ impl RuntimeBindingsMeta { )) } + /// Register QM31 binary operation function if necessary and invoke it. + /// The operation depends on the `op` argument which could indicate: + /// - Add operation + /// - Sub operation + /// - Mul operation + /// - Div operation + /// + /// Executes the operation on the `QM31` values referenced by `lhs_ptr` and `rhs_ptr`, + /// and returns the resulting `QM31`. + #[allow(clippy::too_many_arguments)] + pub fn libfunc_qm31_bin_op<'c, 'a>( + &mut self, + context: &'c Context, + module: &Module, + block: &'a Block<'c>, + lhs_ptr: Value<'c, '_>, + rhs_ptr: Value<'c, '_>, + op: QM31BinaryOperator, + location: Location<'c>, + ) -> Result> + where + 'c: 'a, + { + let qm31_ty = llvm::r#type::array(IntegerType::new(context, 31).into(), 4); + let res_ptr = block.alloca1(context, location, qm31_ty, get_integer_layout(31).align())?; + + let function = match op { + QM31BinaryOperator::Add => { + self.build_function(context, module, block, location, RuntimeBinding::QM31Add)? + } + QM31BinaryOperator::Sub => { + self.build_function(context, module, block, location, RuntimeBinding::QM31Sub)? + } + QM31BinaryOperator::Mul => { + self.build_function(context, module, block, location, RuntimeBinding::QM31Mul)? + } + QM31BinaryOperator::Div => { + self.build_function(context, module, block, location, RuntimeBinding::QM31Div)? + } + }; + + block.append_operation( + OperationBuilder::new("llvm.call", location) + .add_operands(&[function]) + .add_operands(&[lhs_ptr, rhs_ptr, res_ptr]) + .build()?, + ); + + Ok(block.load(context, location, res_ptr, qm31_ty)?) + } + /// Register if necessary, then invoke the `dict_alloc_new()` function. /// /// Returns a opaque pointer as the result. @@ -799,6 +872,10 @@ pub fn setup_runtime(find_symbol_ptr: impl Fn(&str) -> Option<*mut c_void>) { RuntimeBinding::DictDup, RuntimeBinding::GetCostsBuiltin, RuntimeBinding::DebugPrint, + RuntimeBinding::QM31Add, + RuntimeBinding::QM31Sub, + RuntimeBinding::QM31Mul, + RuntimeBinding::QM31Div, #[cfg(feature = "with-cheatcode")] RuntimeBinding::VtableCheatcode, ] { diff --git a/src/runtime.rs b/src/runtime.rs index 249d562fa..496100e92 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -5,6 +5,7 @@ use cairo_lang_sierra_gas::core_libfunc_cost::{ DICT_SQUASH_REPEATED_ACCESS_COST, DICT_SQUASH_UNIQUE_KEY_COST, }; use itertools::Itertools; +use lambdaworks_math::field::fields::mersenne31::extensions::Degree4ExtensionField; use lazy_static::lazy_static; use num_bigint::BigInt; use num_traits::{ToPrimitive, Zero}; @@ -14,6 +15,7 @@ use starknet_types_core::{ curve::{AffinePoint, ProjectivePoint}, felt::Felt, hash::StarkHash, + qm31::QM31, }; use std::{ alloc::{dealloc, realloc, Layout}, @@ -583,6 +585,100 @@ pub unsafe extern "C" fn cairo_native__libfunc__ec__ec_state_try_finalize_nz( } } +/// Compute `qm31_add(qm31, qm31)` and store the result. +/// +/// # Safety +/// +/// This function is intended to be called from MLIR, deals with pointers, and is therefore +/// definitely unsafe to use manually. +pub unsafe extern "C" fn cairo_native__libfunc__qm31__qm31_add( + lhs: &[u32; 4], + rhs: &[u32; 4], + res: &mut [u32; 4], +) { + // We can use this way of creating the QM31 since we already know from cairo that the + // coefficients will never be more than 31 bits wide + let lhs = QM31(Degree4ExtensionField::const_from_coefficients( + lhs[0], lhs[1], lhs[2], lhs[3], + )); + let rhs = QM31(Degree4ExtensionField::const_from_coefficients( + rhs[0], rhs[1], rhs[2], rhs[3], + )); + + *res = to_representative_coefficients(lhs + rhs); +} + +/// Compute `qm31_sub(qm31, qm31)` and store the result. +/// +/// # Safety +/// +/// This function is intended to be called from MLIR, deals with pointers, and is therefore +/// definitely unsafe to use manually. +pub unsafe extern "C" fn cairo_native__libfunc__qm31__qm31_sub( + lhs: &[u32; 4], + rhs: &[u32; 4], + res: &mut [u32; 4], +) { + // We can use this way of creating the QM31 since we already know from cairo that the + // coefficients will never be more than 31 bits wide + let lhs = QM31(Degree4ExtensionField::const_from_coefficients( + lhs[0], lhs[1], lhs[2], lhs[3], + )); + let rhs = QM31(Degree4ExtensionField::const_from_coefficients( + rhs[0], rhs[1], rhs[2], rhs[3], + )); + + *res = to_representative_coefficients(lhs - rhs); +} + +/// Compute `qm31_mul(qm31, qm31)` and store the result. +/// +/// # Safety +/// +/// This function is intended to be called from MLIR, deals with pointers, and is therefore +/// definitely unsafe to use manually. +pub unsafe extern "C" fn cairo_native__libfunc__qm31__qm31_mul( + lhs: &[u32; 4], + rhs: &[u32; 4], + res: &mut [u32; 4], +) { + // We can use this way of creating the QM31 since we already know from cairo that the + // coefficients will never be more than 31 bits wide + let lhs = QM31(Degree4ExtensionField::const_from_coefficients( + lhs[0], lhs[1], lhs[2], lhs[3], + )); + let rhs = QM31(Degree4ExtensionField::const_from_coefficients( + rhs[0], rhs[1], rhs[2], rhs[3], + )); + + *res = to_representative_coefficients(lhs * rhs); +} + +/// Compute `qm31_div(qm31, qm31)` and store the result. +/// +/// # Safety +/// +/// This function is intended to be called from MLIR, deals with pointers, and is therefore +/// definitely unsafe to use manually. +pub unsafe extern "C" fn cairo_native__libfunc__qm31__qm31_div( + lhs: &[u32; 4], + rhs: &[u32; 4], + res: &mut [u32; 4], +) { + // We can use this way of creating the QM31 since we already know from cairo that the + // coefficients will never be more than 31 bits wide + let lhs = QM31(Degree4ExtensionField::const_from_coefficients( + lhs[0], lhs[1], lhs[2], lhs[3], + )); + let rhs = QM31(Degree4ExtensionField::const_from_coefficients( + rhs[0], rhs[1], rhs[2], rhs[3], + )); + + // SAFETY: An error would be triggered here only if rhs is zero. However, in the QM31 division libfunc, the divisor + // is of type NonZero which ensures that we are not falling into the error case. + *res = to_representative_coefficients((lhs / rhs).expect("rhs should not be a QM31 0")); +} + thread_local! { pub(crate) static BUILTIN_COSTS: Cell = const { // These default values shouldn't be accessible, they will be overriden before entering @@ -599,6 +695,25 @@ thread_local! { }; } +// TODO: This is already implemented on types-rs but there is no release +// that contains it. It should be deleted when bumping to a new version +// and use the .to_coefficients() method from QM31 instead. +pub fn to_representative_coefficients(qm31: QM31) -> [u32; 4] { + // Take CM31 coordinates from QM31. + let [a, b] = qm31.0.value(); + + // Take M31 coordinates from both CM31. + let [c1, c2] = a.value(); + let [c3, c4] = b.value(); + + [ + c1.representative(), + c2.representative(), + c3.representative(), + c4.representative(), + ] +} + /// Get the costs builtin from the internal thread local. pub extern "C" fn cairo_native__get_costs_builtin() -> *const [u64; 7] { BUILTIN_COSTS.with(|x| x.as_ptr()) as *const [u64; 7] diff --git a/src/types.rs b/src/types.rs index 72fedf180..b942a3db1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -51,6 +51,7 @@ mod non_zero; mod nullable; mod pedersen; mod poseidon; +mod qm31; mod range_check; mod segment_arena; mod snapshot; @@ -466,7 +467,9 @@ impl TypeBuilder for CoreTypeConcrete { WithSelf::new(self_ty, info), ), Self::Blake(_) => native_panic!("Build Blake type"), - CoreTypeConcrete::QM31(_) => native_panic!("Build QM31 type"), + CoreTypeConcrete::QM31(info) => { + self::qm31::build(context, module, registry, metadata, info) + } CoreTypeConcrete::GasReserve(_) => native_panic!("Build GasReserve type"), } } @@ -522,13 +525,13 @@ impl TypeBuilder for CoreTypeConcrete { | CoreTypeConcrete::Nullable(_) | CoreTypeConcrete::Felt252Dict(_) | CoreTypeConcrete::SquashedFelt252Dict(_) => false, - CoreTypeConcrete::Array(_) => true, CoreTypeConcrete::EcPoint(_) => true, CoreTypeConcrete::EcState(_) => true, CoreTypeConcrete::Felt252DictEntry(_) => true, CoreTypeConcrete::Felt252(_) + | CoreTypeConcrete::QM31(_) | CoreTypeConcrete::Bytes31(_) | CoreTypeConcrete::Starknet( StarknetTypeConcrete::ClassHash(_) @@ -575,7 +578,6 @@ impl TypeBuilder for CoreTypeConcrete { CoreTypeConcrete::IntRange(_info) => false, CoreTypeConcrete::Blake(_info) => native_panic!("Implement is_complex for Blake type"), - CoreTypeConcrete::QM31(_info) => native_panic!("Implement is_complex for QM31 type"), CoreTypeConcrete::GasReserve(_info) => native_panic!("Implement is_complex for GasReserve type"), }) } @@ -623,6 +625,7 @@ impl TypeBuilder for CoreTypeConcrete { | CoreTypeConcrete::SquashedFelt252Dict(_) | CoreTypeConcrete::Starknet(_) | CoreTypeConcrete::Nullable(_) => false, + CoreTypeConcrete::QM31(_) => false, // Containers: CoreTypeConcrete::NonZero(info) @@ -662,7 +665,6 @@ impl TypeBuilder for CoreTypeConcrete { type_info.is_zst(registry)? } CoreTypeConcrete::Blake(_info) => native_panic!("Implement is_zst for Blake type"), - CoreTypeConcrete::QM31(_info) => native_panic!("Implement is_zst for QM31 type"), CoreTypeConcrete::GasReserve(_info) => { native_panic!("Implement is_zst for GasReserve type") } @@ -764,6 +766,7 @@ impl TypeBuilder for CoreTypeConcrete { CoreTypeConcrete::Sint128(_) => get_integer_layout(128), CoreTypeConcrete::Bytes31(_) => get_integer_layout(248), CoreTypeConcrete::BoundedInt(info) => get_integer_layout(info.range.offset_bit_width()), + CoreTypeConcrete::QM31(_info) => layout_repeat(&get_integer_layout(31), 4)?.0, CoreTypeConcrete::Const(const_type) => { registry.get_type(&const_type.inner_ty)?.layout(registry)? @@ -777,7 +780,6 @@ impl TypeBuilder for CoreTypeConcrete { inner.extend(inner)?.0 } CoreTypeConcrete::Blake(_info) => native_panic!("Implement layout for Blake type"), - CoreTypeConcrete::QM31(_info) => native_panic!("Implement layout for QM31 type"), CoreTypeConcrete::GasReserve(_info) => { native_panic!("Implement layout for GasReserve type") } @@ -871,7 +873,7 @@ impl TypeBuilder for CoreTypeConcrete { .is_memory_allocated(registry)?, CoreTypeConcrete::Coupon(_) => false, CoreTypeConcrete::Circuit(_) => false, - CoreTypeConcrete::QM31(_) => native_panic!("Implement is_memory_allocated for QM31"), + CoreTypeConcrete::QM31(_) => false, CoreTypeConcrete::GasReserve(_) => { native_panic!("Implement is_memory_allocated for GasReserve") } diff --git a/src/types/qm31.rs b/src/types/qm31.rs new file mode 100644 index 000000000..0a09c5e05 --- /dev/null +++ b/src/types/qm31.rs @@ -0,0 +1,27 @@ +use crate::{error::Result, metadata::MetadataStorage}; +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType}, + types::InfoOnlyConcreteType, + }, + program_registry::ProgramRegistry, +}; +use melior::{ + dialect::llvm, + ir::{r#type::IntegerType, Module, Type}, + Context, +}; + +/// Build the MLIR type. +/// +/// Check out [the module](self) for more info. +pub fn build<'ctx>( + context: &'ctx Context, + _module: &Module<'ctx>, + _registry: &ProgramRegistry, + _metadata: &mut MetadataStorage, + _info: &InfoOnlyConcreteType, +) -> Result> { + let m31 = IntegerType::new(context, 31).into(); + Ok(llvm::r#type::array(m31, 4)) +} diff --git a/src/values.rs b/src/values.rs index c561cf6ba..fd2a5e442 100644 --- a/src/values.rs +++ b/src/values.rs @@ -79,6 +79,7 @@ pub enum Value { Sint128(i128), EcPoint(Felt, Felt), EcState(Felt, Felt, Felt, Felt), + QM31(u32, u32, u32, u32), Secp256K1Point(Secp256k1Point), Secp256R1Point(Secp256r1Point), BoundedInt { @@ -544,6 +545,7 @@ impl Value { ptr } + Self::QM31(_, _, _, _) => native_panic!("todo: allocate type QM31"), Self::Secp256K1Point { .. } => native_panic!("todo: allocate type Secp256K1Point"), Self::Secp256R1Point { .. } => native_panic!("todo: allocate type Secp256R1Point"), Self::Null => { @@ -736,6 +738,10 @@ impl Value { Felt::from_bytes_le(&data[3]), ) } + CoreTypeConcrete::QM31(_) => { + let data = ptr.cast::<[u32; 4]>().as_mut(); + Self::QM31(data[0], data[1], data[2], data[3]) + } CoreTypeConcrete::Felt252(_) => { let data = ptr.cast::<[u8; 32]>().as_mut(); data[31] &= 0x0F; // Filter out first 4 bits (they're outside an i252). @@ -1017,7 +1023,6 @@ impl Value { } } CoreTypeConcrete::Blake(_) => native_panic!("Implement from_ptr for Blake type"), - CoreTypeConcrete::QM31(_) => native_panic!("Implement from_ptr for QM31 type"), CoreTypeConcrete::GasReserve(_) => { native_panic!("Implement from_ptr for GasReserve type") }