Skip to content

Commit 7b0316a

Browse files
committed
Use sign bit for big integers (instead of 2-complement)
Some operations in 2-complement turns to be quite complex, they require more code, and also more stack space for storing abs value. Hence using a dedicated sign bit (as Erlang does) turns to be an easier and pragmatic approach. This approach makes possible having sign bit outside the numeric payload, so the supported range is -(2^256 - 1)..+(2^256 - 1). They might be called int257, but it would be quite confusing. Sign bit is stored in boxed header, outside of the numeric payload. Also add a valgrind supression file, in order to ignore a bogus warning about overlapping memory in memcpy when executing memmove (that allows overlapping memory). Signed-off-by: Davide Bettio <davide@uninstall.it>
1 parent 406c564 commit 7b0316a

File tree

10 files changed

+459
-356
lines changed

10 files changed

+459
-356
lines changed

.github/workflows/build-and-test.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ jobs:
380380
working-directory: build
381381
run: |
382382
ulimit -c unlimited
383-
valgrind --error-exitcode=1 ./tests/test-erlang -s prime_smp
383+
valgrind --suppressions=../tests/valgrind-suppressions.sup --error-exitcode=1 ./tests/test-erlang -s prime_smp
384384
./tests/test-erlang -s prime_smp
385385
386386
- name: "Test: test-enif"
@@ -418,30 +418,30 @@ jobs:
418418
run: |
419419
ulimit -c unlimited
420420
./src/AtomVM ./tests/libs/etest/test_etest.avm
421-
valgrind ./src/AtomVM ./tests/libs/etest/test_etest.avm
421+
valgrind --suppressions=../tests/valgrind-suppressions.sup ./src/AtomVM ./tests/libs/etest/test_etest.avm
422422
423423
- name: "Test: test_estdlib.avm"
424424
timeout-minutes: 5
425425
working-directory: build
426426
run: |
427427
ulimit -c unlimited
428-
valgrind --error-exitcode=1 ./src/AtomVM ./tests/libs/estdlib/test_estdlib.avm
428+
valgrind --suppressions=../tests/valgrind-suppressions.sup --error-exitcode=1 ./src/AtomVM ./tests/libs/estdlib/test_estdlib.avm
429429
./src/AtomVM ./tests/libs/estdlib/test_estdlib.avm
430430
431431
- name: "Test: test_eavmlib.avm"
432432
timeout-minutes: 10
433433
working-directory: build
434434
run: |
435435
ulimit -c unlimited
436-
valgrind --error-exitcode=1 ./src/AtomVM ./tests/libs/eavmlib/test_eavmlib.avm
436+
valgrind --suppressions=../tests/valgrind-suppressions.sup --error-exitcode=1 ./src/AtomVM ./tests/libs/eavmlib/test_eavmlib.avm
437437
./src/AtomVM ./tests/libs/eavmlib/test_eavmlib.avm
438438
439439
- name: "Test: test_alisp.avm"
440440
timeout-minutes: 10
441441
working-directory: build
442442
run: |
443443
ulimit -c unlimited
444-
valgrind --error-exitcode=1 ./src/AtomVM ./tests/libs/alisp/test_alisp.avm
444+
valgrind --suppressions=../tests/valgrind-suppressions.sup --error-exitcode=1 ./src/AtomVM ./tests/libs/alisp/test_alisp.avm
445445
./src/AtomVM ./tests/libs/alisp/test_alisp.avm
446446
447447
- name: "Test: Tests.avm (Elixir)"
@@ -451,7 +451,7 @@ jobs:
451451
ulimit -c unlimited
452452
if command -v elixirc >/dev/null 2>&1 && command -v elixir >/dev/null 2>&1
453453
then
454-
valgrind --error-exitcode=1 ./src/AtomVM ./tests/libs/exavmlib/Tests.avm
454+
valgrind --suppressions=../tests/valgrind-suppressions.sup --error-exitcode=1 ./src/AtomVM ./tests/libs/exavmlib/Tests.avm
455455
./src/AtomVM ./tests/libs/exavmlib/Tests.avm
456456
else
457457
echo "Elixir not installed, skipping Elixir tests"

src/libAtomVM/bif.c

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@
6161

6262
#define MAX(a, b) ((a) > (b) ? (a) : (b))
6363

64+
// intn.h and term.h headers are decoupled. We check here that sign enum values are matching.
65+
_Static_assert(
66+
(int) TermPositiveInteger == (int) IntNPositiveInteger, "term/intn definition mismatch");
67+
_Static_assert(
68+
(int) TermNegativeInteger == (int) IntNNegativeInteger, "term/intn definition mismatch");
69+
6470
const struct ExportedFunction *bif_registry_get_handler(AtomString module, AtomString function, int arity)
6571
{
6672
char bifname[MAX_BIF_NAME_LEN];
@@ -695,19 +701,30 @@ static inline void intn_to_term_size(size_t n, size_t *intn_data_size, size_t *r
695701
size_t bytes = n * sizeof(intn_digit_t);
696702
size_t rounded = ((bytes + 7) >> 3) << 3;
697703
*intn_data_size = rounded / sizeof(term);
704+
705+
if (*intn_data_size == BOXED_TERMS_REQUIRED_FOR_INT64) {
706+
// we need to distinguish between "small" boxed integers, that are integers
707+
// up to int64, and bigger integers.
708+
// The real difference is that "small" boxed integers use 2-complement,
709+
// real bigints not (and also endianess might differ).
710+
// So we force real bigints to be > BOXED_TERMS_REQUIRED_FOR_INT64 terms
711+
*intn_data_size = BOXED_TERMS_REQUIRED_FOR_INT64 + 1;
712+
rounded = *intn_data_size * sizeof(term);
713+
}
714+
698715
*rounded_num_len = rounded / sizeof(intn_digit_t);
699716
}
700717

701718
static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live,
702-
const intn_digit_t bigres[], size_t bigres_len)
719+
const intn_digit_t bigres[], size_t bigres_len, intn_integer_sign_t sign)
703720
{
704721
size_t count = intn_count_digits(bigres, bigres_len);
705722

706723
if (UNLIKELY(count > INTN_MAX_IN_LEN)) {
707724
RAISE_ERROR_BIF(fail_label, OVERFLOW_ATOM);
708725
}
709726

710-
if (count > INTN_INT64_LEN) {
727+
if (!intn_fits_int64(bigres, count, sign)) {
711728
size_t intn_data_size;
712729
size_t rounded_res_len;
713730
intn_to_term_size(count, &intn_data_size, &rounded_res_len);
@@ -718,13 +735,14 @@ static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live,
718735
RAISE_ERROR_BIF(fail_label, OUT_OF_MEMORY_ATOM);
719736
}
720737

721-
term bigres_term = term_create_uninitialized_intn(intn_data_size, &ctx->heap);
738+
term bigres_term = term_create_uninitialized_intn(
739+
intn_data_size, (term_integer_sign_t) sign, &ctx->heap);
722740
intn_digit_t *dest_buf = (void *) term_intn_data(bigres_term);
723-
intn_sign_extend(bigres, count, rounded_res_len, dest_buf);
741+
intn_copy(bigres, count, dest_buf, rounded_res_len);
724742

725743
return bigres_term;
726744
} else {
727-
int64_t res64 = intn_2_digits_to_int64(bigres, count);
745+
int64_t res64 = intn_2_digits_to_int64(bigres, count, sign);
728746
#if BOXED_TERMS_REQUIRED_FOR_INT64 > 1
729747
return make_maybe_boxed_int64(ctx, fail_label, live, res64);
730748
#else
@@ -733,36 +751,41 @@ static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live,
733751
}
734752
}
735753

736-
static void term_to_bigint(term arg1, intn_digit_t *tmp_buf1, intn_digit_t **b1, size_t *b1_len)
754+
static void term_to_bigint(term arg1, intn_digit_t *tmp_buf1, intn_digit_t **b1, size_t *b1_len,
755+
intn_integer_sign_t *b1_sign)
737756
{
738757
if (term_is_boxed_integer(arg1)
739758
&& (term_boxed_size(arg1) > (INTN_INT64_LEN * sizeof(intn_digit_t)) / sizeof(term))) {
740759
*b1 = term_intn_data(arg1);
741760
*b1_len = term_intn_size(arg1) * (sizeof(term) / sizeof(intn_digit_t));
761+
*b1_sign = (intn_integer_sign_t) term_boxed_integer_sign(arg1);
742762
} else {
743763
avm_int64_t i64 = term_maybe_unbox_int64(arg1);
744-
int64_to_intn_2(i64, tmp_buf1);
764+
int64_to_intn_2(i64, tmp_buf1, b1_sign);
745765
*b1 = tmp_buf1;
746766
*b1_len = INTN_INT64_LEN;
747767
}
748768
}
749769

750770
static void args_to_bigint(term arg1, term arg2, intn_digit_t *tmp_buf1, intn_digit_t *tmp_buf2,
751-
intn_digit_t **b1, size_t *b1_len, intn_digit_t **b2, size_t *b2_len)
771+
intn_digit_t **b1, size_t *b1_len, intn_integer_sign_t *b1_sign, intn_digit_t **b2,
772+
size_t *b2_len, intn_integer_sign_t *b2_sign)
752773
{
753774
// arg1 or arg2 may need to be "upgraded",
754775
// in that case tmp_buf will hold the "upgraded" version
755-
term_to_bigint(arg1, tmp_buf1, b1, b1_len);
756-
term_to_bigint(arg2, tmp_buf2, b2, b2_len);
776+
term_to_bigint(arg1, tmp_buf1, b1, b1_len, b1_sign);
777+
term_to_bigint(arg2, tmp_buf2, b2, b2_len, b2_sign);
757778
}
758779

759780
static term mul_int64_to_bigint(
760781
Context *ctx, uint32_t fail_label, uint32_t live, int64_t val1, int64_t val2)
761782
{
762783
size_t mul_out_len = INTN_MUL_OUT_LEN(INTN_INT64_LEN, INTN_INT64_LEN);
763784
intn_digit_t mul_out[mul_out_len];
764-
intn_mul_int64(val1, val2, mul_out);
765-
return make_bigint(ctx, fail_label, live, mul_out, mul_out_len);
785+
intn_integer_sign_t out_sign;
786+
intn_mul_int64(val1, val2, mul_out, &out_sign);
787+
788+
return make_bigint(ctx, fail_label, live, mul_out, mul_out_len, out_sign);
766789
}
767790

768791
static term mul_maybe_bigint(Context *ctx, uint32_t fail_label, uint32_t live, term arg1, term arg2)
@@ -772,19 +795,23 @@ static term mul_maybe_bigint(Context *ctx, uint32_t fail_label, uint32_t live, t
772795

773796
intn_digit_t *bn1;
774797
size_t bn1_len;
798+
intn_integer_sign_t bn1_sign;
775799
intn_digit_t *bn2;
776800
size_t bn2_len;
777-
args_to_bigint(arg1, arg2, tmp_buf1, tmp_buf2, &bn1, &bn1_len, &bn2, &bn2_len);
801+
intn_integer_sign_t bn2_sign;
802+
args_to_bigint(
803+
arg1, arg2, tmp_buf1, tmp_buf2, &bn1, &bn1_len, &bn1_sign, &bn2, &bn2_len, &bn2_sign);
778804

779805
size_t bigres_len = INTN_MUL_OUT_LEN(bn1_len, bn2_len);
780806
if (bigres_len > INTN_MAX_RES_LEN) {
781807
RAISE_ERROR_BIF(fail_label, OVERFLOW_ATOM);
782808
}
783809

784810
intn_digit_t bigres[INTN_MAX_RES_LEN];
785-
intn_mulmns(bn1, bn1_len, bn2, bn2_len, bigres);
811+
intn_mulmnu(bn1, bn1_len, bn2, bn2_len, bigres);
812+
intn_integer_sign_t res_sign = intn_muldiv_sign(bn1_sign, bn2_sign);
786813

787-
return make_bigint(ctx, fail_label, live, bigres, bigres_len);
814+
return make_bigint(ctx, fail_label, live, bigres, bigres_len, res_sign);
788815
}
789816

790817
static term mul_overflow_helper(

0 commit comments

Comments
 (0)