//! OE / XER[OV] / XER[SO] handling for integer arithmetic.
//!
//! PPC integer ops with the OE bit set update XER[OV] (overflow) and sticky-set
//! XER[SO]. When OE is clear the instruction leaves XER untouched. Signed
//! overflow is predicated on the operation width and operand signs per the
//! PowerISA pseudocode. For 32-bit-word operations (`addw`, `mullw`, `divw`,
//! `neg`, etc. — on PPC these all have `w` in the mnemonic in spec
//! descriptions even when the assembler spells them without) the predicate
//! uses the low 32 bits. For 64-bit operations (`add`, `mulld`, `divd`) the
//! predicate uses the full 64 bits.

use crate::context::PpcContext;

#[inline]
pub fn apply(ctx: &mut PpcContext, overflowed: bool) {
    if overflowed {
        ctx.xer_ov = 1;
        ctx.xer_so = 1;
    } else {
        ctx.xer_ov = 0;
    }
}

/// Signed addition overflow at width-64 (plain `add`, `addc`, `subf`, `subfc`).
///
/// Predicate: same-sign inputs with opposite-sign result.
/// For sub callers, rewrite as `a + b'` first (see `_sub`).
#[inline]
pub fn add_ov_64(a: u64, b: u64, result: u64) -> bool {
    ((!(a ^ b)) & (a ^ result)) >> 63 != 0
}

/// Universal signed-overflow predicate for 64-bit arithmetic.
///
/// Caller computes the mathematical (infinite-precision) signed sum as i128,
/// plus the stored 64-bit result. Overflow iff the two disagree — i.e. the
/// true value doesn't fit in i64.
///
/// Use this for multi-term chains (`adde`, `addme`, `addze`, `subfe`, `subfme`,
/// `subfze`) where the carry-in makes the bit-predicate above awkward.
#[inline]
pub fn sum_overflow_64(true_sum: i128, result: u64) -> bool {
    true_sum != (result as i64) as i128
}

/// Signed subtraction: RT = b - a. Overflow iff opposite-sign inputs with
/// result sign != b's sign. Equivalently, reduce to addition with `!a + 1`.
#[inline]
pub fn sub_ov_64(a: u64, b: u64, result: u64) -> bool {
    ((a ^ b) & (b ^ result)) >> 63 != 0
}

/// Signed `addc`/`adde` chain overflow. Same rule as `add_ov_64` — the carry
/// in doesn't alter the sign predicate directly because it's already folded
/// into the stored result.
#[inline]
pub fn adde_ov_64(a: u64, b: u64, result: u64) -> bool {
    add_ov_64(a, b, result)
}

/// Signed 32-bit multiply overflow (`mullwo`): result fits in 32 bits signed
/// iff bit 32 equals bits 33..63 of the 64-bit product.
#[inline]
pub fn mullw_ov(product: i64) -> bool {
    let lo = product as i32 as i64;
    lo != product
}

/// Signed 64-bit multiply overflow (`mulldo`). Detected via checked_mul.
#[inline]
pub fn mulld_ov(a: i64, b: i64) -> bool {
    a.checked_mul(b).is_none()
}

/// `divwo` / `divwuo` / `divdo` / `divduo` raise OV in two cases:
///   * divisor is zero, or
///   * signed division of `INT_MIN / -1` (quotient doesn't fit).
#[inline]
pub fn divw_ov_signed(ra: i32, rb: i32) -> bool {
    rb == 0 || (ra == i32::MIN && rb == -1)
}

#[inline]
pub fn divw_ov_unsigned(rb: u32) -> bool {
    rb == 0
}

#[inline]
pub fn divd_ov_signed(ra: i64, rb: i64) -> bool {
    rb == 0 || (ra == i64::MIN && rb == -1)
}

#[inline]
pub fn divd_ov_unsigned(rb: u64) -> bool {
    rb == 0
}

/// `negx`: RT = -(RA). Overflow only when RA = INT_MIN (the negation doesn't fit).
#[inline]
pub fn neg_ov_64(ra: u64) -> bool {
    ra == 0x8000_0000_0000_0000
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn add_no_overflow() {
        assert!(!add_ov_64(1, 2, 3));
        assert!(!add_ov_64(u64::MAX, 0, u64::MAX));
    }

    #[test]
    fn add_positive_overflow() {
        // INT64_MAX + 1 = INT64_MIN — signed overflow
        let a = i64::MAX as u64;
        let b = 1u64;
        let r = a.wrapping_add(b);
        assert!(add_ov_64(a, b, r));
    }

    #[test]
    fn add_negative_overflow() {
        // INT64_MIN + -1 = INT64_MAX — signed overflow
        let a = i64::MIN as u64;
        let b = (-1i64) as u64;
        let r = a.wrapping_add(b);
        assert!(add_ov_64(a, b, r));
    }

    #[test]
    fn sub_overflow_min_minus_pos() {
        // INT64_MIN - 1 overflows
        let b = i64::MIN as u64;
        let a = 1u64;
        let r = b.wrapping_sub(a);
        assert!(sub_ov_64(a, b, r));
    }

    #[test]
    fn sub_no_overflow() {
        let b = 5u64;
        let a = 2u64;
        let r = b.wrapping_sub(a);
        assert!(!sub_ov_64(a, b, r));
    }

    #[test]
    fn mullw_fits_32_bits() {
        assert!(!mullw_ov((i32::MAX as i64) * 1));
        assert!(!mullw_ov(-1i64));
    }

    #[test]
    fn mullw_overflows_32_bits() {
        let p = (i32::MAX as i64) * 2;
        assert!(mullw_ov(p));
    }

    #[test]
    fn mulld_overflows() {
        assert!(mulld_ov(i64::MAX, 2));
        assert!(!mulld_ov(i64::MAX, 1));
        // PPCBUG-022: INT_MIN * -1 overflows (=-INT_MIN > INT_MAX).
        // checked_mul correctly returns None for this case.
        assert!(mulld_ov(i64::MIN, -1), "INT_MIN * -1 overflows i64");
        assert!(!mulld_ov(i64::MIN, 1));
        assert!(!mulld_ov(i64::MIN + 1, -1), "INT_MIN+1 * -1 = INT_MAX, no overflow");
    }

    #[test]
    fn neg_ov_only_at_min() {
        assert!(neg_ov_64(i64::MIN as u64));
        assert!(!neg_ov_64(0));
        assert!(!neg_ov_64(1));
    }
}
