diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d1ef0f1a..d4b2e1cc5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed `term_is_uint32` accepting big integers whose low 64 bits are within range on 32-bit builds, which made `erlang:crc32/2`, `erlang:crc32_combine/3` and `crypto:pbkdf2_hmac/5` silently truncate huge integer arguments instead of raising `badarg` +- Fixed a bug where bigints were not normalized, yielding equality errors ## [0.7.0-alpha.1] - 2026-04-06 diff --git a/src/libAtomVM/external_term.c b/src/libAtomVM/external_term.c index f306e2e810..1c36fa9cd5 100644 --- a/src/libAtomVM/external_term.c +++ b/src/libAtomVM/external_term.c @@ -712,11 +712,16 @@ static term parse_external_terms(const uint8_t *external_term_buf, size_t *eterm AVM_ABORT(); } + intn_integer_sign_t sign = is_negative ? IntNNegativeInteger : IntNPositiveInteger; + count = (int) intn_count_digits(bigint, count); + if (intn_fits_int64(bigint, count, sign)) { + return term_make_maybe_boxed_int64(intn_to_int64(bigint, count, sign), heap); + } + size_t intn_data_size; size_t rounded_res_len; term_bigint_size_requirements(count, &intn_data_size, &rounded_res_len); - intn_integer_sign_t sign = is_negative ? IntNNegativeInteger : IntNPositiveInteger; term bigint_term = term_create_uninitialized_bigint(intn_data_size, (term_integer_sign_t) sign, heap); term_initialize_bigint(bigint_term, bigint, count, rounded_res_len); diff --git a/src/libAtomVM/jit.c b/src/libAtomVM/jit.c index 30798f5cf1..3bacc09be0 100644 --- a/src/libAtomVM/jit.c +++ b/src/libAtomVM/jit.c @@ -1446,6 +1446,16 @@ static term jit_term_alloc_bin_match_state(Context *ctx, term src, int slots) static term make_bigint_from_digits( Context *ctx, JITState *jit_state, intn_digit_t *bigint, intn_integer_sign_t sign, int count) { + count = (int) intn_count_digits(bigint, count); + if (intn_fits_int64(bigint, count, sign)) { + term t = maybe_alloc_boxed_integer_fragment(ctx, intn_to_int64(bigint, count, sign)); + if (UNLIKELY(term_is_invalid_term(t))) { + set_error(ctx, jit_state, 0, OUT_OF_MEMORY_ATOM); + return FALSE_ATOM; + } + return t; + } + size_t intn_data_size; size_t rounded_res_len; term_bigint_size_requirements(count, &intn_data_size, &rounded_res_len); diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index 413311fba5..5cfc453f0e 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -1572,6 +1572,11 @@ static bool maybe_call_native(Context *ctx, atom_index_t module_name, atom_index #ifndef AVM_NO_EMU static term make_bigint_from_digits(Context *ctx, intn_digit_t *bigint, intn_integer_sign_t sign, size_t count) { + count = intn_count_digits(bigint, count); + if (intn_fits_int64(bigint, count, sign)) { + return maybe_alloc_boxed_integer_fragment(ctx, intn_to_int64(bigint, count, sign)); + } + size_t intn_data_size; size_t rounded_res_len; term_bigint_size_requirements(count, &intn_data_size, &rounded_res_len); diff --git a/tests/erlang_tests/CMakeLists.txt b/tests/erlang_tests/CMakeLists.txt index 96fc78d8fb..5128415a1d 100644 --- a/tests/erlang_tests/CMakeLists.txt +++ b/tests/erlang_tests/CMakeLists.txt @@ -438,6 +438,7 @@ compile_erlang(test_list_tuple_eq) compile_erlang(test_ref_eq) compile_erlang(test_binary_eq) compile_erlang(test_bigint_eq) +compile_erlang(test_bigint_normalization) compile_erlang(test_binaries_ordering) compile_erlang(test_lists_ordering) @@ -1003,6 +1004,7 @@ set(erlang_test_beams test_ref_eq.beam test_binary_eq.beam test_bigint_eq.beam + test_bigint_normalization.beam test_binaries_ordering.beam test_lists_ordering.beam diff --git a/tests/erlang_tests/test_bigint_normalization.erl b/tests/erlang_tests/test_bigint_normalization.erl new file mode 100644 index 0000000000..dbe11c8c77 --- /dev/null +++ b/tests/erlang_tests/test_bigint_normalization.erl @@ -0,0 +1,93 @@ +% +% This file is part of AtomVM. +% +% Copyright 2026 Paul Guyot +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +-module(test_bigint_normalization). + +-export([start/0, id/1]). + +start() -> + ok = test_small_value_from_wide_segment(), + ok = test_negative_small_value_from_wide_segment(), + ok = test_zero_from_wide_segment(), + ok = test_large_value_stays_big(), + ok = test_normalized_value_in_containers(), + ok = test_wide_small_big_ext(), + 0. + +test_small_value_from_wide_segment() -> + A = u72(id(<<42:72>>)), + true = is_integer(A), + true = A =:= 42, + true = A == 42, + 42 = A, + 0 = A - 42, + 84 = A + 42, + ok. + +test_negative_small_value_from_wide_segment() -> + B = s72(id(<<-42:72>>)), + true = B =:= -42, + true = B == -42, + -42 = B, + ok. + +test_zero_from_wide_segment() -> + Z = u72(id(<<0:72>>)), + true = Z =:= 0, + 0 = Z, + ok. + +test_large_value_stays_big() -> + C = u72(id(<<(1 bsl 70):72>>)), + true = C =:= (1 bsl 70), + false = C =:= 42, + %% 1 bsl 62 exceeds the small integer range on every supported word size + D = u72(id(<<(1 bsl 62):72>>)), + true = D =:= (1 bsl 62), + ok. + +test_normalized_value_in_containers() -> + A = u72(id(<<42:72>>)), + true = lists:member(A, [41, 42, 43]), + [42] = [A], + {ok, 42} = {ok, A}, + true = [A] =:= [42], + ok. + +test_wide_small_big_ext() -> + P = binary_to_term(id(<<131, 110, 9, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0>>)), + true = P =:= 42, + 42 = P, + N = binary_to_term(id(<<131, 110, 9, 1, 42, 0, 0, 0, 0, 0, 0, 0, 0>>)), + true = N =:= -42, + -42 = N, + Big = binary_to_term(id(<<131, 110, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64>>)), + true = Big =:= (1 bsl 70), + ok. + +u72(Bin) -> + <> = Bin, + X. + +s72(Bin) -> + <> = Bin, + X. + +id(X) -> X. diff --git a/tests/test.c b/tests/test.c index 18ae322d2a..6db82e2c8c 100644 --- a/tests/test.c +++ b/tests/test.c @@ -395,6 +395,7 @@ struct Test tests[] = { TEST_CASE_EXPECTED(test_ref_eq, 1), TEST_CASE_EXPECTED(test_binary_eq, 1), TEST_CASE_EXPECTED(test_bigint_eq, 1), + TEST_CASE(test_bigint_normalization), TEST_CASE_EXPECTED(test_binaries_ordering, 15), TEST_CASE_EXPECTED(test_lists_ordering, 7),