Skip to content

Commit 5e2c8b6

Browse files
refactor(cast): unify downcast felt252 canonicalization branches (#1653)
Both arms of the felt252 canonicalization in build_downcast reduce to "subtract PRIME when the felt exceeds a threshold": 0 for a non-positive destination range, HALF_PRIME for one that straddles zero. This also fixes a VM divergence: the non-positive branch subtracted PRIME unconditionally, interpreting felt 0 as -PRIME. For a destination whose lower bound is exactly 1 - PRIME no lower-bound check is emitted, so downcast<felt252, BoundedInt<1-P, U<=0>>(0) returned Some where the VM returns None (and under-charged the range check builtin, 2 vs 3). With the 0 threshold, felt 0 stays 0 and is rejected by the upper-bound check, matching the VM on both the value and the builtin counter. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 0aaf5bf commit 5e2c8b6

1 file changed

Lines changed: 21 additions & 19 deletions

File tree

src/libfuncs/cast.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use melior::{
2424
ir::{r#type::IntegerType, Block, Location, Value, ValueLike},
2525
Context,
2626
};
27-
use num_bigint::{BigInt, Sign};
27+
use num_bigint::{BigInt, BigUint, Sign};
2828
use num_traits::One;
2929

3030
/// Select and call the correct libfunc builder function from the selector.
@@ -145,26 +145,28 @@ pub fn build_downcast<'ctx, 'this>(
145145
// 2. if it is a bounded_int, we need to offset the value to get the
146146
// actual value.
147147
let src_value = if is_signed && src_ty.is_felt252(registry)? {
148-
if src_range.upper.is_one() {
149-
let adj_offset =
150-
entry.const_int_from_type(context, location, PRIME.clone(), src_value.r#type())?;
151-
entry.append_op_result(arith::subi(src_value, adj_offset, location))?
152-
} else {
153-
let adj_offset = entry.const_int_from_type(
154-
context,
155-
location,
156-
HALF_PRIME.clone(),
157-
src_value.r#type(),
158-
)?;
159-
let is_negative =
160-
entry.cmpi(context, CmpiPredicate::Ugt, src_value, adj_offset, location)?;
148+
// A felt is interpreted as negative (`felt - PRIME`) when it exceeds
149+
// a threshold: HALF_PRIME when the destination range straddles zero,
150+
// and 0 when it is non-positive (every nonzero felt is negative,
151+
// while felt 0 stays 0 and must fail the bounds check).
152+
let adj_offset = entry.const_int_from_type(
153+
context,
154+
location,
155+
if src_range.upper.is_one() {
156+
BigUint::ZERO
157+
} else {
158+
HALF_PRIME.clone()
159+
},
160+
src_value.r#type(),
161+
)?;
162+
let is_negative =
163+
entry.cmpi(context, CmpiPredicate::Ugt, src_value, adj_offset, location)?;
161164

162-
let k_prime =
163-
entry.const_int_from_type(context, location, PRIME.clone(), src_value.r#type())?;
164-
let adj_value = entry.append_op_result(arith::subi(src_value, k_prime, location))?;
165+
let k_prime =
166+
entry.const_int_from_type(context, location, PRIME.clone(), src_value.r#type())?;
167+
let adj_value = entry.append_op_result(arith::subi(src_value, k_prime, location))?;
165168

166-
entry.append_op_result(arith::select(is_negative, adj_value, src_value, location))?
167-
}
169+
entry.append_op_result(arith::select(is_negative, adj_value, src_value, location))?
168170
} else if src_ty.is_bounded_int(registry)? && src_range.lower != BigInt::ZERO {
169171
let dst_offset = entry.const_int_from_type(
170172
context,

0 commit comments

Comments
 (0)