From cf09a8542990476c11645c734fa86dc021741d99 Mon Sep 17 00:00:00 2001 From: Diego Civini Date: Mon, 11 Aug 2025 17:56:44 -0300 Subject: [PATCH 1/4] First iteration of mlir euclidean algorithm --- src/compiler.rs | 127 +++++++++++++++++++++++++++++++++++++++- src/libfuncs/circuit.rs | 64 +++++++++++++++++--- 2 files changed, 181 insertions(+), 10 deletions(-) diff --git a/src/compiler.rs b/src/compiler.rs index 21a5d822d..2c8af596c 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -74,7 +74,7 @@ use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use itertools::Itertools; use melior::{ dialect::{ - arith::CmpiPredicate, + arith::{self, CmpiPredicate}, cf, func, index, llvm::{self, LoadStoreOptions}, memref, @@ -141,6 +141,23 @@ pub fn compile( } } + let location = Location::unknown(context); // TODO: WHICH LOCATION SHOULD I USE? + let integer_type: Type = IntegerType::new(context, 384 * 2).into(); + let region = declare_euclidean_func(context, location, integer_type); + let func_name = StringAttribute::new(context, "cairo_native__euclidean_algorithm"); + module.body().append_operation(llvm::func( + context, + func_name, + TypeAttribute::new(llvm::r#type::function( + llvm::r#type::r#struct(context, &[integer_type, integer_type], false), + &[integer_type, integer_type], + false, + )), + region, + &[], + location, + )); + // Sierra programs have the following structure: // 1. Type declarations, one per line. // 2. Libfunc declarations, one per line. @@ -171,6 +188,114 @@ pub fn compile( Ok(()) } +fn declare_euclidean_func<'ctx>( + context: &'ctx Context, + location: Location<'ctx>, + integer_type: Type<'_>, +) -> Region<'ctx> { + let region = Region::new(); + + let entry_block = region.append_block(Block::new(&[ + (integer_type, location), + (integer_type, location), + ])); + + // The algorithm egcd works by calculating a series of remainders, each the remainder of dividing the previous two + // For the initial setup, r0 = b, r1 = a + // This order is chosen because if we reverse them, then the first iteration will just swap them + let remainder = entry_block.arg(0).unwrap(); + let prev_remainder = entry_block.arg(1).unwrap(); + + // Similarly we'll calculate another series which starts 0,1,... and from which we will retrieve the modular inverse of a + let prev_inverse = entry_block + .const_int_from_type(context, location, 0, integer_type) + .unwrap(); + let inverse = entry_block + .const_int_from_type(context, location, 1, integer_type) + .unwrap(); + + let loop_block = region.append_block(Block::new(&[ + (integer_type, location), + (integer_type, location), + (integer_type, location), + (integer_type, location), + ])); + let end_block = region.append_block(Block::new(&[ + (integer_type, location), + (integer_type, location), + ])); + + entry_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).unwrap(); + let remainder = loop_block.arg(1).unwrap(); + let prev_inverse = loop_block.arg(2).unwrap(); + let inverse = loop_block.arg(3).unwrap(); + + // First calculate q = rem_(i-1)/rem_i, rounded down + let quotient = loop_block + .append_op_result(arith::divui(prev_remainder, remainder, location)) + .unwrap(); + + // 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).unwrap(); + let inv_times_quo = loop_block.muli(inverse, quotient, location).unwrap(); + let next_remainder = loop_block + .append_op_result(arith::subi(prev_remainder, rem_times_quo, location)) + .unwrap(); + let next_inverse = loop_block + .append_op_result(arith::subi(prev_inverse, inv_times_quo, location)) + .unwrap(); + + // Check if r_(i+1) is 0 + // If true, then: + // - r_i is the gcd of a and b + // - inv_i is the bezout coefficient x + + let zero = loop_block + .const_int_from_type(context, location, 0, integer_type) + .unwrap(); + let next_remainder_eq_zero = loop_block + .cmpi(context, CmpiPredicate::Eq, next_remainder, zero, location) + .unwrap(); + loop_block.append_operation(cf::cond_br( + context, + next_remainder_eq_zero, + &end_block, + &loop_block, + &[remainder, inverse], + &[remainder, next_remainder, inverse, next_inverse], + location, + )); + // loop_block.append_operation(cf::br(&end_block, &[remainder, inverse], location)); + + //////// SAME AS libsfuncs/array.rs line 213 //////// + let results = end_block + .append_op_result(llvm::undef( + llvm::r#type::r#struct(context, &[integer_type, integer_type], false), + location, + )) + .unwrap(); + let results = end_block + .insert_values( + context, + location, + results, + &[end_block.arg(0).unwrap(), end_block.arg(1).unwrap()], + ) + .unwrap(); + //////////////////////////////////////////////// + end_block.append_operation(llvm::r#return(Some(results), location)); + + region +} + /// Compile a single Sierra function. /// /// The function accepts a `Function` argument, which provides the function's entry point, signature diff --git a/src/libfuncs/circuit.rs b/src/libfuncs/circuit.rs index c7bc1b479..85d429745 100644 --- a/src/libfuncs/circuit.rs +++ b/src/libfuncs/circuit.rs @@ -32,7 +32,10 @@ use melior::{ cf, llvm, }, helpers::{ArithBlockExt, BuiltinBlockExt, GepIndex, LlvmBlockExt}, - ir::{r#type::IntegerType, Block, BlockLike, Location, Type, Value, ValueLike}, + ir::{ + attribute::FlatSymbolRefAttribute, operation::OperationBuilder, r#type::IntegerType, Block, + BlockLike, Identifier, Location, Type, Value, ValueLike, + }, Context, }; use num_traits::Signed; @@ -634,17 +637,33 @@ fn build_gate_evaluation<'ctx, 'this>( let integer_type = rhs_value.r#type(); // Apply egcd to find gcd and inverse - let egcd_result_block = build_euclidean_algorithm( + let euclidean_result = + call_euclidean_func(context, block, location, rhs_value, circuit_modulus); + let gcd = block.extract_value( context, - block, location, - helper, - rhs_value, - circuit_modulus, + euclidean_result, + integer_type, + 0, + )?; + let inverse = block.extract_value( + context, + location, + euclidean_result, + integer_type, + 1, )?; - let gcd = egcd_result_block.arg(0)?; - let inverse = egcd_result_block.arg(1)?; - block = egcd_result_block; + // let egcd_result_block = build_euclidean_algorithm( + // context, + // block, + // location, + // helper, + // rhs_value, + // circuit_modulus, + // )?; + // let gcd = egcd_result_block.arg(0)?; + // let inverse = egcd_result_block.arg(1)?; + // block = egcd_result_block; // if the gcd is not 1, then fail (a and b are not coprimes) let one = block.const_int_from_type(context, location, 1, integer_type)?; @@ -713,6 +732,33 @@ fn build_gate_evaluation<'ctx, 'this>( Ok(([block, err_block], values)) } +fn call_euclidean_func<'ctx>( + context: &'ctx Context, + block: &'ctx Block<'ctx>, + location: Location<'ctx>, + a: Value<'ctx, '_>, + b: Value<'ctx, '_>, +) -> Value<'ctx, 'ctx> { + let integer_type: Type = IntegerType::new(context, 384 * 2).into(); + let return_type = llvm::r#type::r#struct(context, &[integer_type, integer_type], false); + block + .append_operation( + OperationBuilder::new("llvm.call", location) + .add_attributes(&[( + Identifier::new(context, "callee"), + FlatSymbolRefAttribute::new(context, "cairo_native__euclidean_algorithm") + .into(), + )]) + .add_operands(&[a, b]) + .add_results(&[return_type]) + .build() + .unwrap(), + ) + .result(0) + .unwrap() + .into() +} + /// Generate MLIR operations for the `circuit_failure_guarantee_verify` libfunc. /// NOOP #[allow(clippy::too_many_arguments)] From 684187fb4acccdff6c57beb8663d76ad6a80d2a2 Mon Sep 17 00:00:00 2001 From: Diego Civini Date: Tue, 12 Aug 2025 09:35:47 -0300 Subject: [PATCH 2/4] Delete unused function --- src/libfuncs/circuit.rs | 81 ----------------------------------------- 1 file changed, 81 deletions(-) diff --git a/src/libfuncs/circuit.rs b/src/libfuncs/circuit.rs index 85d429745..73e89252a 100644 --- a/src/libfuncs/circuit.rs +++ b/src/libfuncs/circuit.rs @@ -1080,87 +1080,6 @@ fn u384_integer_to_struct<'a>( )?) } -/// The extended euclidean algorithm calculates the greatest common divisor (gcd) of two integers a and b, -/// as well as the bezout coefficients x and y such that ax+by=gcd(a,b) -/// if gcd(a,b) = 1, then x is the modular multiplicative inverse of a modulo b. -/// See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm -/// -/// Given two numbers a, b. It returns a block with gcd(a, b) and the bezout coefficient x. -fn build_euclidean_algorithm<'ctx, 'this>( - context: &'ctx Context, - block: &'ctx Block<'ctx>, - location: Location<'ctx>, - helper: &LibfuncHelper<'ctx, 'this>, - a: Value<'ctx, 'ctx>, - b: Value<'ctx, 'ctx>, -) -> Result<&'this Block<'ctx>> { - let integer_type = a.r#type(); - - let loop_block = helper.append_block(Block::new(&[ - (integer_type, location), - (integer_type, location), - (integer_type, location), - (integer_type, location), - ])); - let end_block = helper.append_block(Block::new(&[ - (integer_type, location), - (integer_type, location), - ])); - - // The algorithm egcd works by calculating a series of remainders, each the remainder of dividing the previous two - // For the initial setup, r0 = b, r1 = a - // This order is chosen because if we reverse them, then the first iteration will just swap them - let prev_remainder = b; - let remainder = a; - // Similarly we'll calculate another series which starts 0,1,... and from which we will retrieve the modular inverse of a - let prev_inverse = block.const_int_from_type(context, location, 0, integer_type)?; - let inverse = block.const_int_from_type(context, location, 1, integer_type)?; - 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))?; - - // Check if r_(i+1) is 0 - // If true, then: - // - r_i is the gcd of a and b - // - inv_i is the bezout coefficient x - - let zero = loop_block.const_int_from_type(context, location, 0, integer_type)?; - 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, - end_block, - loop_block, - &[remainder, inverse], - &[remainder, next_remainder, inverse, next_inverse], - location, - )); - - Ok(end_block) -} - /// Extracts values from indexes `from` - `to` (exclusive) and builds a new value of type `result_type` /// /// Can be used with arrays, or structs with multiple elements of a single type. From bba8476bbc4fd815e90e2e14aba2adccf18f2303 Mon Sep 17 00:00:00 2001 From: Diego Civini Date: Tue, 12 Aug 2025 13:13:14 -0300 Subject: [PATCH 3/4] Add no_inline attribute --- src/compiler.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler.rs b/src/compiler.rs index 2c8af596c..d230d1068 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -154,7 +154,10 @@ pub fn compile( false, )), region, - &[], + &[( + Identifier::new(context, "no_inline"), + Attribute::unit(context), + )], location, )); From b2a4a6626e87a2319143e4321949550b013ac0cb Mon Sep 17 00:00:00 2001 From: Diego Civini Date: Tue, 12 Aug 2025 14:34:55 -0300 Subject: [PATCH 4/4] Add no_inline attr to call --- src/libfuncs/circuit.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/libfuncs/circuit.rs b/src/libfuncs/circuit.rs index 73e89252a..efa2865e2 100644 --- a/src/libfuncs/circuit.rs +++ b/src/libfuncs/circuit.rs @@ -33,8 +33,8 @@ use melior::{ }, helpers::{ArithBlockExt, BuiltinBlockExt, GepIndex, LlvmBlockExt}, ir::{ - attribute::FlatSymbolRefAttribute, operation::OperationBuilder, r#type::IntegerType, Block, - BlockLike, Identifier, Location, Type, Value, ValueLike, + attribute::FlatSymbolRefAttribute, operation::OperationBuilder, r#type::IntegerType, + Attribute, Block, BlockLike, Identifier, Location, Type, Value, ValueLike, }, Context, }; @@ -744,11 +744,17 @@ fn call_euclidean_func<'ctx>( block .append_operation( OperationBuilder::new("llvm.call", location) - .add_attributes(&[( - Identifier::new(context, "callee"), - FlatSymbolRefAttribute::new(context, "cairo_native__euclidean_algorithm") - .into(), - )]) + .add_attributes(&[ + ( + Identifier::new(context, "callee"), + FlatSymbolRefAttribute::new(context, "cairo_native__euclidean_algorithm") + .into(), + ), + ( + Identifier::new(context, "no_inline"), + Attribute::unit(context), + ), + ]) .add_operands(&[a, b]) .add_results(&[return_type]) .build()