Skip to content

Commit 83a408a

Browse files
refactor: share comparison-predicate selection across int libfuncs (#1627)
`int_range_try_new`, `int_range_pop_front`, and `bounded_int_constrain` each picked between signed/unsigned cmpi predicates with near-identical inline logic. Lift it into a `compare_predicate` helper on `src/libfuncs.rs` that takes the operand's `CoreTypeConcrete`, the registry, and the (signed, unsigned) predicate pair, and routes to whichever variant matches the stored representation: `i8..i128` is two's complement, while `u*` and `BoundedInt<L, U>` are stored as non-negative integers (the latter biased to start at zero regardless of `L`'s sign). Adds a VM-equivalence test (`int_range_try_new_bounded_int_negative_lower_vm_equivalence`) over `BoundedInt<-10, 10>` covering 7 input pairs around the 5-bit MSB threshold. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 07d0bc0 commit 83a408a

6 files changed

Lines changed: 100 additions & 23 deletions

File tree

src/libfuncs.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ use cairo_lang_sierra::{
2626
};
2727
use itertools::Itertools;
2828
use melior::{
29-
dialect::{arith, cf, llvm, ods},
29+
dialect::{
30+
arith::{self},
31+
cf, llvm, ods,
32+
},
3033
helpers::{ArithBlockExt, BuiltinBlockExt, LlvmBlockExt},
3134
ir::{
3235
attribute::{FlatSymbolRefAttribute, StringAttribute, TypeAttribute},
@@ -546,6 +549,19 @@ fn increment_builtin_counter_conditionally_by<'ctx: 'a, 'a>(
546549
))?)
547550
}
548551

552+
/// Whether the operand's stored representation is two's-complement signed,
553+
/// so callers know to pick the signed `cmpi` variant over the unsigned one.
554+
///
555+
/// Plain Sierra signed ints (`i8`..`i128`) live as two's complement.
556+
/// Everything else — `u*` and `BoundedInt<L, U>`, which is biased to start at
557+
/// zero regardless of `L`'s sign — is stored as a non-negative integer.
558+
fn is_signed_repr(
559+
ty: &CoreTypeConcrete,
560+
registry: &ProgramRegistry<CoreType, CoreLibfunc>,
561+
) -> Result<bool> {
562+
Ok(ty.integer_range(registry)?.lower < BigInt::ZERO && !ty.is_bounded_int(registry)?)
563+
}
564+
549565
fn build_noop<'ctx, 'this, const N: usize, const PROCESS_BUILTINS: bool>(
550566
context: &'ctx Context,
551567
registry: &ProgramRegistry<CoreType, CoreLibfunc>,

src/libfuncs/bounded_int.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -573,12 +573,11 @@ fn build_constrain<'ctx, 'this>(
573573
src_value.r#type(),
574574
)?;
575575

576-
let cmpi_predicate =
577-
if src.ty.is_bounded_int(registry)? || src.range.lower.sign() != Sign::Minus {
578-
CmpiPredicate::Ult
579-
} else {
580-
CmpiPredicate::Slt
581-
};
576+
let cmpi_predicate = if super::is_signed_repr(src.ty, registry)? {
577+
CmpiPredicate::Slt
578+
} else {
579+
CmpiPredicate::Ult
580+
};
582581
let is_lower = entry.cmpi(context, cmpi_predicate, src_value, boundary, location)?;
583582

584583
let lower_block = helper.append_block(Block::new(&[]));

src/libfuncs/int_range.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
//! # Int range libfuncs
22
33
use super::LibfuncHelper;
4-
use crate::{
5-
error::Result, metadata::MetadataStorage, types::TypeBuilder, utils::ProgramRegistryExt,
6-
};
4+
use crate::{error::Result, metadata::MetadataStorage, utils::ProgramRegistryExt};
75
use cairo_lang_sierra::{
86
extensions::{
97
core::{CoreLibfunc, CoreType},
@@ -22,7 +20,6 @@ use melior::{
2220
ir::{Block, Location},
2321
Context,
2422
};
25-
use num_bigint::BigInt;
2623

2724
/// Select and call the correct libfunc builder function from the selector.
2825
pub fn build<'ctx, 'this>(
@@ -66,14 +63,12 @@ pub fn build_int_range_try_new<'ctx, 'this>(
6663
&info.branch_signatures()[0].vars[1].ty,
6764
)?;
6865
let inner = registry.get_type(&info.param_signatures()[1].ty)?;
69-
// to know if it is signed
70-
let inner_range = inner.integer_range(registry)?;
71-
72-
let is_valid = if inner_range.lower < BigInt::ZERO {
73-
entry.cmpi(context, CmpiPredicate::Sle, x, y, location)?
66+
let predicate = if super::is_signed_repr(inner, registry)? {
67+
CmpiPredicate::Sle
7468
} else {
75-
entry.cmpi(context, CmpiPredicate::Ule, x, y, location)?
69+
CmpiPredicate::Ule
7670
};
71+
let is_valid = entry.cmpi(context, predicate, x, y, location)?;
7772

7873
let range =
7974
entry.append_op_result(ods::llvm::mlir_undef(context, range_ty, location).into())?;
@@ -118,14 +113,12 @@ pub fn build_int_range_pop_front<'ctx, 'this>(
118113
let x_p_1 = entry.addi(x, k1, location)?;
119114
let y = entry.extract_value(context, location, range, inner_ty, 1)?;
120115

121-
// to know if it is signed
122-
let inner_range = inner.integer_range(registry)?;
123-
124-
let is_valid = if inner_range.lower < BigInt::ZERO {
125-
entry.cmpi(context, CmpiPredicate::Slt, x, y, location)?
116+
let predicate = if super::is_signed_repr(inner, registry)? {
117+
CmpiPredicate::Slt
126118
} else {
127-
entry.cmpi(context, CmpiPredicate::Ult, x, y, location)?
119+
CmpiPredicate::Ult
128120
};
121+
let is_valid = entry.cmpi(context, predicate, x, y, location)?;
129122
let range = entry.insert_value(context, location, range, x_p_1, 0)?;
130123

131124
helper.cond_br(
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#[feature("bounded-int-utils")]
2+
use core::internal::bounded_int::BoundedInt;
3+
4+
pub extern type IntRange<T>;
5+
impl IntRangeDrop<T> of Drop<IntRange<T>>;
6+
7+
pub extern fn int_range_try_new<T>(
8+
x: T, y: T,
9+
) -> Result<IntRange<T>, IntRange<T>> implicits(core::RangeCheck) nopanic;
10+
11+
fn run_test(lhs: felt252, rhs: felt252) -> bool {
12+
let lhs: BoundedInt<-10, 10> = lhs.try_into().unwrap();
13+
let rhs: BoundedInt<-10, 10> = rhs.try_into().unwrap();
14+
match int_range_try_new(lhs, rhs) {
15+
Result::Ok(_) => true,
16+
Result::Err(_) => false,
17+
}
18+
}

tests/tests/int_range.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use crate::common::{compare_outputs, run_native_program, run_vm_program, DEFAULT_GAS};
2+
use cairo_lang_runner::Arg;
3+
use cairo_native::starknet::DummySyscallHandler;
4+
use cairo_native::utils::testing::load_program_and_runner;
5+
use cairo_native::Value;
6+
use starknet_types_core::felt::Felt;
7+
use test_case::test_case;
8+
9+
// `int_range_try_new` over `BoundedInt<-10, 10>` (5-bit biased representation,
10+
// bias = +10) must compare biased values with an unsigned predicate. A signed
11+
// predicate misreads any biased value with MSB set as negative; e.g. `(10, 0)`
12+
// -> biased `(20, 10)` is read as `(-12, 10)` and the invalid range is
13+
// incorrectly accepted, while `(-10, 10)` -> biased `(0, 20)` is read as
14+
// `(0, -12)` and the valid range is rejected.
15+
#[test_case(0, 5; "valid_zero_to_five")]
16+
#[test_case(-10, -5; "valid_neg_ten_to_neg_five")]
17+
#[test_case(-10, 10; "valid_full_range")]
18+
#[test_case(10, 10; "valid_empty_at_upper")]
19+
#[test_case(0, -5; "invalid_descending_zero")]
20+
#[test_case(10, 0; "invalid_msb_biased_lhs")]
21+
#[test_case(10, -10; "invalid_max_to_min")]
22+
fn int_range_try_new_bounded_int_negative_lower_vm_equivalence(lhs: i64, rhs: i64) {
23+
let program = &load_program_and_runner("programs/libfuncs/int_range_try_new_bounded_int");
24+
25+
let result_vm = run_vm_program(
26+
program,
27+
"run_test",
28+
vec![Arg::Value(Felt::from(lhs)), Arg::Value(Felt::from(rhs))],
29+
Some(DEFAULT_GAS as usize),
30+
)
31+
.unwrap();
32+
let result_native = run_native_program(
33+
program,
34+
"run_test",
35+
&[
36+
Value::Felt252(Felt::from(lhs)),
37+
Value::Felt252(Felt::from(rhs)),
38+
],
39+
Some(DEFAULT_GAS),
40+
Option::<DummySyscallHandler>::None,
41+
);
42+
43+
compare_outputs(
44+
&program.1,
45+
&program.2.find_function("run_test").unwrap().id,
46+
&result_vm,
47+
&result_native,
48+
)
49+
.unwrap();
50+
}

tests/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod dict;
88
pub mod e2e_libfuncs;
99
pub mod ec;
1010
pub mod felt252;
11+
pub mod int_range;
1112
pub mod libfuncs;
1213
pub mod programs;
1314
pub mod result;

0 commit comments

Comments
 (0)