Zulip Chat Archive

Stream: lean4

Topic: Int.negSucc


Zygimantas Straznickas (Mar 07 2021 at 19:20):

Is Int supposed to be arbitrary precision, like Nat? There's some weirdness with Int.negSucc around Int64Max:

-- (9223372036854775807 == Int64Max)
#eval Int.negSucc 9223372036854775807 -- 0
#eval Int.negSucc 9223372036854775807 == 0 -- false
#eval Int.negSucc 9223372036854775807 == Int64Min -- false
#reduce Int.negSucc 9223372036854775807 == Int64Min -- true
#eval (Int.negSucc 9223372036854775807) + 1 == 1 -- true
#reduce (Int.negSucc 9223372036854775807) + 1 == 1 -- true

Mario Carneiro (Mar 07 2021 at 19:35):

Oh that's bad. Is it another proof of false?

Mario Carneiro (Mar 07 2021 at 19:37):

Not a proof of false, just a bad extern

Zygimantas Straznickas (Mar 07 2021 at 19:38):

Yup, I don’t think the kernel uses Int

Mario Carneiro (Mar 07 2021 at 19:42):

I suspect https://github.com/leanprover/lean4/blob/master/src/include/lean/lean.h#L1301-L1302 but that should also affect Nat.succ

Mario Carneiro (Mar 07 2021 at 19:45):

Where did you get Int64Min? I don't see it on the master branch

Mario Carneiro (Mar 07 2021 at 19:48):

I am also not replicating your result of the first #eval. I get various numbers nondeterministically like -84788720, -67524080, -72164848 which seems worse

Julian Berman (Mar 07 2021 at 19:49):

(I get seg fault on Lean (version 4.0.0-nightly-2021-03-06, commit be4cf605fde9, Release) )

Mario Carneiro (Mar 07 2021 at 19:50):

I suspect this is hitting C undefined behavior

Mario Carneiro (Mar 07 2021 at 19:51):

probably the negation here when given INT_MIN

Zygimantas Straznickas (Mar 07 2021 at 19:56):

Where did you get Int64Min? I don't see it on the master branch

Just my helper definitions:

def UInt32Max : Nat := UInt32.size - 1
def UInt64Max : Nat := UInt64.size - 1
def Int32Max : Int := (Int.ofNat ((UInt32.size / 2))) - 1
def Int32Min : Int := -1*(Int.ofNat ((UInt32.size / 2)))
def Int64Max : Int := (Int.ofNat ((UInt64.size / 2))) - 1
def Int64Min : Int := -1*(Int.ofNat ((UInt64.size / 2)))

One other weird thing: if I take https://github.com/leanprover/lean4/blob/master/src/include/lean/lean.h#L1509 (impl of Int.negSucc) and replace it with a Lean-defined function that just calls these three native functions, everything works

Mario Carneiro (Mar 07 2021 at 20:17):

I think I see the error. The code does this:

    lean_obj_res s  = lean_nat_succ(a);    lean_dec(a);
    lean_obj_res i  = lean_nat_to_int(s);  lean_dec(s);
    lean_obj_res r  = lean_int_neg(i);     lean_dec(i);

Here a is 2^63-1. The first step is lean_nat_succ:

    if (LEAN_LIKELY(lean_is_scalar(a)))
        return lean_usize_to_nat(lean_unbox(a) + 1);
    else
        ...

Since a is exactly the largest small nat, lean_unbox(a) + 1 does not overflow and produces 2^63. Since this is now too big for a nat, lean_usize_to_nat returns a new mpz with value 2^63.

The second step, lean_nat_to_int, does nothing because it's already an mpz.

The third step, lean_int_neg, calls lean_int_big_neg:

    return mpz_to_int(neg(mpz_value(a)));

neg is mpz negation and produces -2^63, assuming GMP is implemented correctly, so the last step is mpz_to_int:

    if (m < LEAN_MIN_SMALL_INT || m > LEAN_MAX_SMALL_INT)
        return mpz_to_int_core(m);
    else
        return lean_box(static_cast<unsigned>(m.get_int()));

I'm not 100% sure those inequalities are working correctly, because < is only overloaded for int, unsigned and unsigned long, so it might be casting them to unsigned longs or something, c++ integer promotion is weird. Assuming they work as advertised, we should go into the else branch, where we have more suspicious code: m.get_int() returns an int and is cast to an unsigned and promoted to a size_t, so if int and unsigned are 32 bit as is normal then this seems like it will hit UB (or at least overflow).

Leonardo de Moura (Mar 07 2021 at 21:11):

Pushed a fix for this bug
https://github.com/leanprover/lean4/commit/8afa96d6c89d7e34e372dda2d2b58abb1378cf8f


Last updated: Dec 20 2023 at 11:08 UTC