Skip to content

Commit

Permalink
Merge pull request #2777 from adri326/fix-2772-rnd_i-clipping
Browse files Browse the repository at this point in the history
Fix invalid casts in is/2
  • Loading branch information
mthom authored Jan 27, 2025
2 parents 28678e7 + 0cde8f9 commit 5a869e8
Show file tree
Hide file tree
Showing 6 changed files with 820 additions and 106 deletions.
23 changes: 14 additions & 9 deletions src/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,36 +354,41 @@ impl<'a> ArithmeticEvaluator<'a> {
}

// integer division rounding function -- 9.1.3.1.
pub(crate) fn rnd_i(n: &'_ Number, arena: &mut Arena) -> Number {
pub(crate) fn rnd_i(n: &'_ Number, arena: &mut Arena) -> Result<Number, EvalError> {
match n {
&Number::Integer(i) => {
let result = (&*i).try_into();
if let Ok(value) = result {
fixnum!(Number, value, arena)
Ok(fixnum!(Number, value, arena))
} else {
*n
Ok(*n)
}
}
Number::Fixnum(_) => *n,
Number::Fixnum(_) => Ok(*n),
&Number::Float(f) => {
let f = f.floor();

const I64_MIN_TO_F: OrderedFloat<f64> = OrderedFloat(i64::MIN as f64);
const I64_MAX_TO_F: OrderedFloat<f64> = OrderedFloat(i64::MAX as f64);

if I64_MIN_TO_F <= f && f <= I64_MAX_TO_F {
fixnum!(Number, f.into_inner() as i64, arena)
Ok(fixnum!(Number, f.into_inner() as i64, arena))
} else {
Number::Integer(arena_alloc!(Integer::from(f.0 as i64), arena))
Ok(Number::Integer(arena_alloc!(
Integer::try_from(classify_float(f.0)?).unwrap_or_else(|_| {
unreachable!();
}),
arena
)))
}
}
Number::Rational(ref r) => {
let (_, floor) = (r.fract(), r.floor());
let floor = r.floor();

if let Ok(value) = (&floor).try_into() {
fixnum!(Number, value, arena)
Ok(fixnum!(Number, value, arena))
} else {
Number::Integer(arena_alloc!(floor, arena))
Ok(Number::Integer(arena_alloc!(floor, arena)))
}
}
}
Expand Down
217 changes: 120 additions & 97 deletions src/machine/arithmetic_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,13 +455,8 @@ pub(crate) fn max(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
Ok(Number::Fixnum(n2))
}
}
(Number::Integer(n1), Number::Integer(n2)) => {
if n1 > n2 {
Ok(Number::Integer(n1))
} else {
Ok(Number::Integer(n2))
}
}
(Number::Integer(n1), Number::Integer(n2)) => Ok(Number::Integer(cmp::max(n1, n2))),
(Number::Rational(r1), Number::Rational(r2)) => Ok(Number::Rational(cmp::max(r1, r2))),
(n1, n2) => {
let stub_gen = || {
let max_atom = atom!("max");
Expand All @@ -471,7 +466,15 @@ pub(crate) fn max(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
let f1 = try_numeric_result!(result_f(&n1), stub_gen)?;
let f2 = try_numeric_result!(result_f(&n2), stub_gen)?;

Ok(Number::Float(cmp::max(OrderedFloat(f1), OrderedFloat(f2))))
match OrderedFloat(f1).cmp(&OrderedFloat(f2)) {
cmp::Ordering::Less => Ok(n2),
cmp::Ordering::Equal => {
// Note: n1 and n2 were compared as floats,
// so we return the second argument as a floating point value.
Ok(Number::Float(OrderedFloat(f2)))
}
cmp::Ordering::Greater => Ok(n1),
}
}
}
}
Expand Down Expand Up @@ -499,13 +502,8 @@ pub(crate) fn min(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
Ok(Number::Fixnum(n2))
}
}
(Number::Integer(n1), Number::Integer(n2)) => {
if n1 < n2 {
Ok(Number::Integer(n1))
} else {
Ok(Number::Integer(n2))
}
}
(Number::Integer(n1), Number::Integer(n2)) => Ok(Number::Integer(cmp::min(n1, n2))),
(Number::Rational(r1), Number::Rational(r2)) => Ok(Number::Rational(cmp::min(r1, r2))),
(n1, n2) => {
let stub_gen = || {
let min_atom = atom!("min");
Expand All @@ -515,7 +513,15 @@ pub(crate) fn min(n1: Number, n2: Number) -> Result<Number, MachineStubGen> {
let f1 = try_numeric_result!(result_f(&n1), stub_gen)?;
let f2 = try_numeric_result!(result_f(&n2), stub_gen)?;

Ok(Number::Float(cmp::min(OrderedFloat(f1), OrderedFloat(f2))))
match OrderedFloat(f1).cmp(&OrderedFloat(f2)) {
cmp::Ordering::Less => Ok(n1),
cmp::Ordering::Equal => {
// Note: n1 and n2 were compared as floats,
// so we return the first argument as a floating point value.
Ok(Number::Float(OrderedFloat(f1)))
}
cmp::Ordering::Greater => Ok(n2),
}
}
}
}
Expand Down Expand Up @@ -623,103 +629,110 @@ pub(crate) fn int_floor_div(
idiv(n1, n2, arena)
}

pub(crate) fn shr(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
pub(crate) fn shr(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
let stub_gen = || {
let shr_atom = atom!(">>");
functor_stub(shr_atom, 2)
};

if n2.is_integer() && n2.is_negative() {
return shl(n1, neg(n2, arena), arena);
if rhs.is_integer() && rhs.is_negative() {
return shl(lhs, neg(rhs, arena), arena);
}

match (n1, n2) {
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
let n1_i = n1.get_num();
let n2_i = n2.get_num();

// FIXME(arithmetic_overflow)
// what should this do for too large n2,
// - logical right shift should probably turn to 0
// - arithmetic right shift should maybe differ for negative numbers
//
// note: negaitve n2 is already handled above
#[allow(arithmetic_overflow)]
if let Ok(n2) = usize::try_from(n2_i) {
Ok(Number::arena_from(n1_i >> n2, arena))
} else {
Ok(Number::arena_from(n1_i >> usize::MAX, arena))
}
}
(Number::Fixnum(n1), Number::Integer(n2)) => {
let n1 = Integer::from(n1.get_num());

let result: Result<usize, _> = (&*n2).try_into();
match lhs {
Number::Fixnum(lhs) => {
let rhs = match rhs {
Number::Fixnum(fix) => fix.get_num().try_into().unwrap_or(u32::MAX),
Number::Integer(int) => (&*int).try_into().unwrap_or(u32::MAX),
other => {
return Err(numerical_type_error(ValidType::Integer, other, stub_gen));
}
};

match result {
Ok(n2) => Ok(Number::arena_from(n1 >> n2, arena)),
Err(_) => Ok(Number::arena_from(n1 >> usize::MAX, arena)),
}
}
(Number::Integer(n1), Number::Fixnum(n2)) => match usize::try_from(n2.get_num()) {
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 >> n2), arena)),
_ => Ok(Number::arena_from(Integer::from(&*n1 >> usize::MAX), arena)),
},
(Number::Integer(n1), Number::Integer(n2)) => {
let result: Result<usize, _> = (&*n2).try_into();
let res = lhs.get_num().checked_shr(rhs).unwrap_or(0);
Ok(Number::arena_from(res, arena))
}
Number::Integer(lhs) => {
// Note: bigints require `log(n)` bits of space. If `rhs > usize::MAX`,
// then this clamping only becomes an issue when `lhs < 2 ^ (usize::MAX)`:
// - on 32-bit systems, `lhs` would need to be 512MiB big (1/8th of the addressable memory)
// - on 64-bit systems, `lhs` would need to be 2EiB big (!!!)
let rhs = match rhs {
Number::Fixnum(fix) => fix.get_num().try_into().unwrap_or(usize::MAX),
Number::Integer(int) => (&*int).try_into().unwrap_or(usize::MAX),
other => {
return Err(numerical_type_error(ValidType::Integer, other, stub_gen));
}
};

match result {
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 >> n2), arena)),
Err(_) => Ok(Number::arena_from(Integer::from(&*n1 >> usize::MAX), arena)),
}
Ok(Number::arena_from(Integer::from(&*lhs >> rhs), arena))
}
(Number::Integer(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
(Number::Fixnum(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
(n1, _) => Err(numerical_type_error(ValidType::Integer, n1, stub_gen)),
other => Err(numerical_type_error(ValidType::Integer, other, stub_gen)),
}
}

pub(crate) fn shl(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
pub(crate) fn shl(lhs: Number, rhs: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
let stub_gen = || {
let shl_atom = atom!("<<");
functor_stub(shl_atom, 2)
};

if n2.is_integer() && n2.is_negative() {
return shr(n1, neg(n2, arena), arena);
if rhs.is_integer() && rhs.is_negative() {
return shr(lhs, neg(rhs, arena), arena);
}

match (n1, n2) {
(Number::Fixnum(n1), Number::Fixnum(n2)) => {
let n1_i = n1.get_num();
let n2_i = n2.get_num();
let rhs = match rhs {
Number::Fixnum(fix) => fix.get_num().try_into().unwrap_or(usize::MAX),
Number::Integer(int) => (&*int).try_into().unwrap_or(usize::MAX),
other => {
return Err(numerical_type_error(ValidType::Integer, other, stub_gen));
}
};

match lhs {
Number::Fixnum(lhs) => {
let lhs = lhs.get_num();

if let Ok(n2) = usize::try_from(n2_i) {
Ok(Number::arena_from(n1_i << n2, arena))
if let Some(res) = checked_signed_shl(lhs, rhs) {
Ok(Number::arena_from(res, arena))
} else {
let n1 = Integer::from(n1_i);
Ok(Number::arena_from(n1 << usize::MAX, arena))
let lhs = Integer::from(lhs);
Ok(Number::arena_from(
Integer::from(lhs << (rhs as usize)),
arena,
))
}
}
(Number::Fixnum(n1), Number::Integer(n2)) => {
let n1 = Integer::from(n1.get_num());
Number::Integer(lhs) => Ok(Number::arena_from(
Integer::from(&*lhs << (rhs as usize)),
arena,
)),
other => Err(numerical_type_error(ValidType::Integer, other, stub_gen)),
}
}

match (&*n2).try_into() as Result<usize, _> {
Ok(n2) => Ok(Number::arena_from(n1 << n2, arena)),
_ => Ok(Number::arena_from(n1 << usize::MAX, arena)),
}
/// Returns `x << shift`, checking for overflow and for values of `shift` that are too big.
#[inline]
fn checked_signed_shl(x: i64, shift: usize) -> Option<i64> {
if shift == 0 {
return Some(x);
}

if x >= 0 {
// Note: for unsigned integers, the condition would usually be spelled
// `shift <= x.leading_zeros()`, but since the MSB for signed integers
// controls the sign, we need to make sure that `shift` is at most
// `x.leading_zeros() - 1`.
if shift < x.leading_zeros() as usize {
Some(x << shift)
} else {
None
}
(Number::Integer(n1), Number::Fixnum(n2)) => match usize::try_from(n2.get_num()) {
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 << n2), arena)),
_ => Ok(Number::arena_from(Integer::from(&*n1 << usize::MAX), arena)),
},
(Number::Integer(n1), Number::Integer(n2)) => match (&*n2).try_into() as Result<usize, _> {
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 << n2), arena)),
_ => Ok(Number::arena_from(Integer::from(&*n1 << usize::MAX), arena)),
},
(Number::Integer(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
(Number::Fixnum(_), n2) => Err(numerical_type_error(ValidType::Integer, n2, stub_gen)),
(n1, _) => Err(numerical_type_error(ValidType::Integer, n1, stub_gen)),
} else {
let y = x.checked_neg()?;
// FIXME: incorrectly rejects `-2 ^ 62 << 1`. This is currently a non-issue,
// since the bitshift is then done as a `Number::Integer`
checked_signed_shl(y, shift).and_then(|res| res.checked_neg())
}
}

Expand Down Expand Up @@ -947,8 +960,7 @@ pub(crate) fn gcd(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
Ok(Number::arena_from(Integer::from(n2_clone.gcd(&n1)), arena))
}
(Number::Integer(n1), Number::Integer(n2)) => {
let n2: isize = (&*n2).try_into().unwrap();
let value: Integer = (&*n1).gcd(&Integer::from(n2)).into();
let value: Integer = (&*n1).gcd(&*n2).into();
Ok(Number::arena_from(value, arena))
}
(Number::Float(f), _) | (_, Number::Float(f)) => {
Expand Down Expand Up @@ -1044,7 +1056,12 @@ pub(crate) fn sqrt(n1: Number) -> Result<f64, MachineStubGen> {

#[inline]
pub(crate) fn floor(n1: Number, arena: &mut Arena) -> Number {
rnd_i(&n1, arena)
rnd_i(&n1, arena).unwrap_or_else(|_err| {
// FIXME: Currently floor/1 (and several call sites) are infallible,
// but the failing cases (when `n1` is `Number::Float(NAN)` or `Number::Float(INFINITY)`)
// are not reachable with standard is/2 operations.
todo!("Make floor/1 fallible");
})
}

#[inline]
Expand All @@ -1067,16 +1084,22 @@ pub(crate) fn truncate(n: Number, arena: &mut Arena) -> Number {
}
}

pub(crate) fn round(n: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
let stub_gen = || {
let is_atom = atom!("is");
functor_stub(is_atom, 2)
pub(crate) fn round(num: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
let res = match num {
Number::Fixnum(_) | Number::Integer(_) => num,
Number::Rational(rat) => Number::arena_from(rat.round(), arena),
Number::Float(f) => Number::Float(OrderedFloat((*f).round())),
};

let result = add(n, Number::Float(OrderedFloat(0.5f64)), arena);
let result = try_numeric_result!(result, stub_gen)?;
// FIXME: make round/1 return EvalError
rnd_i(&res, arena).map_err(|err| -> MachineStubGen {
Box::new(move |machine_st| {
let eval_error = machine_st.evaluation_error(err);
let stub = functor_stub(atom!("round"), 1);

Ok(floor(result, arena))
machine_st.error_form(eval_error, stub)
})
})
}

pub(crate) fn bitwise_complement(n1: Number, arena: &mut Arena) -> Result<Number, MachineStubGen> {
Expand Down
Loading

0 comments on commit 5a869e8

Please sign in to comment.