From 1725da523d1f07749f29870657d7d88398440e43 Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Sat, 25 Oct 2025 17:16:26 +0200 Subject: [PATCH 1/5] bif.c: rename term_to_bigint to conv_term_to_bigint This function name is going to be used from term.h. Also since it is a static helper, put verb first. Signed-off-by: Davide Bettio --- src/libAtomVM/bif.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/libAtomVM/bif.c b/src/libAtomVM/bif.c index 2ac303aab..9864008fb 100644 --- a/src/libAtomVM/bif.c +++ b/src/libAtomVM/bif.c @@ -88,7 +88,7 @@ _Static_assert( static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live, const intn_digit_t bigres[], size_t bigres_len, intn_integer_sign_t sign); -static void term_to_bigint(term arg1, intn_digit_t *tmp_buf1, const intn_digit_t **b1, +static void conv_term_to_bigint(term arg1, intn_digit_t *tmp_buf1, const intn_digit_t **b1, size_t *b1_len, intn_integer_sign_t *b1_sign); const struct ExportedFunction *bif_registry_get_handler(const char *mfa) @@ -555,11 +555,11 @@ static term add_maybe_bigint(Context *ctx, uint32_t fail_label, uint32_t live, t const intn_digit_t *bn1; size_t bn1_len; intn_integer_sign_t bn1_sign; - term_to_bigint(arg1, tmp_buf1, &bn1, &bn1_len, &bn1_sign); + conv_term_to_bigint(arg1, tmp_buf1, &bn1, &bn1_len, &bn1_sign); const intn_digit_t *bn2; size_t bn2_len; intn_integer_sign_t bn2_sign; - term_to_bigint(arg2, tmp_buf2, &bn2, &bn2_len, &bn2_sign); + conv_term_to_bigint(arg2, tmp_buf2, &bn2, &bn2_len, &bn2_sign); size_t bigres_len = INTN_ADD_OUT_LEN(bn1_len, bn2_len); if (bigres_len > INTN_MAX_RES_LEN) { @@ -693,11 +693,11 @@ static term sub_maybe_bigint(Context *ctx, uint32_t fail_label, uint32_t live, t const intn_digit_t *bn1; size_t bn1_len; intn_integer_sign_t bn1_sign; - term_to_bigint(arg1, tmp_buf1, &bn1, &bn1_len, &bn1_sign); + conv_term_to_bigint(arg1, tmp_buf1, &bn1, &bn1_len, &bn1_sign); const intn_digit_t *bn2; size_t bn2_len; intn_integer_sign_t bn2_sign; - term_to_bigint(arg2, tmp_buf2, &bn2, &bn2_len, &bn2_sign); + conv_term_to_bigint(arg2, tmp_buf2, &bn2, &bn2_len, &bn2_sign); size_t bigres_len = INTN_SUB_OUT_LEN(bn1_len, bn2_len); if (bigres_len > INTN_MAX_RES_LEN) { @@ -839,7 +839,7 @@ static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live, } } -static void term_to_bigint(term arg1, intn_digit_t *tmp_buf1, const intn_digit_t **b1, +static void conv_term_to_bigint(term arg1, intn_digit_t *tmp_buf1, const intn_digit_t **b1, size_t *b1_len, intn_integer_sign_t *b1_sign) { if (term_is_boxed_integer(arg1) @@ -874,11 +874,11 @@ static term mul_maybe_bigint(Context *ctx, uint32_t fail_label, uint32_t live, t const intn_digit_t *bn1; size_t bn1_len; intn_integer_sign_t bn1_sign; - term_to_bigint(arg1, tmp_buf1, &bn1, &bn1_len, &bn1_sign); + conv_term_to_bigint(arg1, tmp_buf1, &bn1, &bn1_len, &bn1_sign); const intn_digit_t *bn2; size_t bn2_len; intn_integer_sign_t bn2_sign; - term_to_bigint(arg2, tmp_buf2, &bn2, &bn2_len, &bn2_sign); + conv_term_to_bigint(arg2, tmp_buf2, &bn2, &bn2_len, &bn2_sign); size_t bigres_len = INTN_MUL_OUT_LEN(bn1_len, bn2_len); if (bigres_len > INTN_MAX_RES_LEN) { @@ -1038,11 +1038,11 @@ static term div_maybe_bigint(Context *ctx, uint32_t fail_label, uint32_t live, t const intn_digit_t *bn1; size_t bn1_len; intn_integer_sign_t bn1_sign; - term_to_bigint(arg1, tmp_buf1, &bn1, &bn1_len, &bn1_sign); + conv_term_to_bigint(arg1, tmp_buf1, &bn1, &bn1_len, &bn1_sign); const intn_digit_t *bn2; size_t bn2_len; intn_integer_sign_t bn2_sign; - term_to_bigint(arg2, tmp_buf2, &bn2, &bn2_len, &bn2_sign); + conv_term_to_bigint(arg2, tmp_buf2, &bn2, &bn2_len, &bn2_sign); int cmp_result = intn_cmp(bn1, bn1_len, bn2, bn2_len); if (cmp_result < 0) { @@ -1146,7 +1146,7 @@ term bif_erlang_div_2(Context *ctx, uint32_t fail_label, int live, term arg1, te // that just copies the given term but changes the sign static term neg_bigint(Context *ctx, uint32_t fail_label, uint32_t live, term arg1) { - // update when updating term_to_bigint + // update when updating conv_term_to_bigint intn_digit_t *m = term_intn_data(arg1); size_t m_len = term_intn_size(arg1) * (sizeof(term) / sizeof(intn_digit_t)); intn_integer_sign_t m_sign = (intn_integer_sign_t) term_boxed_integer_sign(arg1); @@ -1242,7 +1242,7 @@ term bif_erlang_neg_1(Context *ctx, uint32_t fail_label, int live, term arg1) // that just copies the given term but changes the sign static term abs_bigint(Context *ctx, uint32_t fail_label, uint32_t live, term arg1) { - // update when updating term_to_bigint + // update when updating conv_term_to_bigint intn_digit_t *m = term_intn_data(arg1); size_t m_len = term_intn_size(arg1) * (sizeof(term) / sizeof(intn_digit_t)); @@ -1359,11 +1359,11 @@ static term rem_maybe_bigint(Context *ctx, uint32_t fail_label, uint32_t live, t const intn_digit_t *bn1; size_t bn1_len; intn_integer_sign_t bn1_sign; - term_to_bigint(arg1, tmp_buf1, &bn1, &bn1_len, &bn1_sign); + conv_term_to_bigint(arg1, tmp_buf1, &bn1, &bn1_len, &bn1_sign); const intn_digit_t *bn2; size_t bn2_len; intn_integer_sign_t bn2_sign; - term_to_bigint(arg2, tmp_buf2, &bn2, &bn2_len, &bn2_sign); + conv_term_to_bigint(arg2, tmp_buf2, &bn2, &bn2_len, &bn2_sign); int cmp_result = intn_cmp(bn1, bn1_len, bn2, bn2_len); if (cmp_result < 0) { @@ -1622,11 +1622,11 @@ static inline term bitwise_helper( const intn_digit_t *m; size_t m_len; intn_integer_sign_t m_sign; - term_to_bigint(arg1, tmp_buf1, &m, &m_len, &m_sign); + conv_term_to_bigint(arg1, tmp_buf1, &m, &m_len, &m_sign); const intn_digit_t *n; size_t n_len; intn_integer_sign_t n_sign; - term_to_bigint(arg2, tmp_buf2, &n, &n_len, &n_sign); + conv_term_to_bigint(arg2, tmp_buf2, &n, &n_len, &n_sign); intn_digit_t bigres[INTN_MAX_RES_LEN]; intn_integer_sign_t bigres_sign; @@ -1702,7 +1702,7 @@ term bif_erlang_bsl_2(Context *ctx, uint32_t fail_label, int live, term arg1, te const intn_digit_t *m; size_t m_len; intn_integer_sign_t m_sign; - term_to_bigint(arg1, tmp_buf1, &m, &m_len, &m_sign); + conv_term_to_bigint(arg1, tmp_buf1, &m, &m_len, &m_sign); intn_digit_t bigres[INTN_MAX_RES_LEN]; size_t bigres_len = intn_bsl(m, m_len, b, bigres); @@ -1762,7 +1762,7 @@ term bif_erlang_bsr_2(Context *ctx, uint32_t fail_label, int live, term arg1, te const intn_digit_t *m; size_t m_len; intn_integer_sign_t m_sign; - term_to_bigint(arg1, tmp_buf1, &m, &m_len, &m_sign); + conv_term_to_bigint(arg1, tmp_buf1, &m, &m_len, &m_sign); intn_digit_t bigres[INTN_MAX_RES_LEN]; size_t bigres_len = intn_bsr(m, m_len, m_sign, b, bigres); @@ -1823,7 +1823,7 @@ static term bnot_boxed_helper(Context *ctx, uint32_t fail_label, uint32_t live, const intn_digit_t *m; size_t m_len; intn_integer_sign_t m_sign; - term_to_bigint(arg1, tmp_buf1, &m, &m_len, &m_sign); + conv_term_to_bigint(arg1, tmp_buf1, &m, &m_len, &m_sign); intn_digit_t bigres[INTN_MAX_RES_LEN]; intn_integer_sign_t bigres_sign; From 83fe7b1d851bb62fd65152fa8981e38653b38bad Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Sun, 26 Oct 2025 12:41:14 +0100 Subject: [PATCH 2/5] Add `term_to_bigint` and `term_is_bigint` Replace duplicated code with new functions in term.h. Signed-off-by: Davide Bettio --- src/libAtomVM/bif.c | 29 +++++------- src/libAtomVM/externalterm.c | 10 ++--- src/libAtomVM/nifs.c | 11 +++-- src/libAtomVM/term.c | 21 +++++---- src/libAtomVM/term.h | 85 +++++++++++++++++++++++++++++++++--- 5 files changed, 109 insertions(+), 47 deletions(-) diff --git a/src/libAtomVM/bif.c b/src/libAtomVM/bif.c index 9864008fb..9d2c89290 100644 --- a/src/libAtomVM/bif.c +++ b/src/libAtomVM/bif.c @@ -79,12 +79,6 @@ #define INT64_MAX_AS_AVM_FLOAT 9223372036854775295.0 // 0x43DFFFFFFFFFFFFF = 2^62 * 1.1...1b #endif -// intn.h and term.h headers are decoupled. We check here that sign enum values are matching. -_Static_assert( - (int) TermPositiveInteger == (int) IntNPositiveInteger, "term/intn definition mismatch"); -_Static_assert( - (int) TermNegativeInteger == (int) IntNNegativeInteger, "term/intn definition mismatch"); - static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live, const intn_digit_t bigres[], size_t bigres_len, intn_integer_sign_t sign); @@ -842,11 +836,9 @@ static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live, static void conv_term_to_bigint(term arg1, intn_digit_t *tmp_buf1, const intn_digit_t **b1, size_t *b1_len, intn_integer_sign_t *b1_sign) { - if (term_is_boxed_integer(arg1) - && (term_boxed_size(arg1) > (INTN_INT64_LEN * sizeof(intn_digit_t)) / sizeof(term))) { - *b1 = term_intn_data(arg1); - *b1_len = term_intn_size(arg1) * (sizeof(term) / sizeof(intn_digit_t)); - *b1_sign = (intn_integer_sign_t) term_boxed_integer_sign(arg1); + if (term_is_bigint(arg1)) { + term_to_bigint(arg1, b1, b1_len, b1_sign); + } else { avm_int64_t i64 = term_maybe_unbox_int64(arg1); intn_from_int64(i64, tmp_buf1, b1_sign); @@ -1146,10 +1138,10 @@ term bif_erlang_div_2(Context *ctx, uint32_t fail_label, int live, term arg1, te // that just copies the given term but changes the sign static term neg_bigint(Context *ctx, uint32_t fail_label, uint32_t live, term arg1) { - // update when updating conv_term_to_bigint - intn_digit_t *m = term_intn_data(arg1); - size_t m_len = term_intn_size(arg1) * (sizeof(term) / sizeof(intn_digit_t)); - intn_integer_sign_t m_sign = (intn_integer_sign_t) term_boxed_integer_sign(arg1); + const intn_digit_t *m; + size_t m_len; + intn_integer_sign_t m_sign; + term_to_bigint(arg1, &m, &m_len, &m_sign); intn_digit_t tmp_copy[INTN_MAX_RES_LEN]; memcpy(tmp_copy, m, m_len * sizeof(intn_digit_t)); @@ -1242,9 +1234,10 @@ term bif_erlang_neg_1(Context *ctx, uint32_t fail_label, int live, term arg1) // that just copies the given term but changes the sign static term abs_bigint(Context *ctx, uint32_t fail_label, uint32_t live, term arg1) { - // update when updating conv_term_to_bigint - intn_digit_t *m = term_intn_data(arg1); - size_t m_len = term_intn_size(arg1) * (sizeof(term) / sizeof(intn_digit_t)); + const intn_digit_t *m; + size_t m_len; + intn_integer_sign_t discarded_sign; + term_to_bigint(arg1, &m, &m_len, &discarded_sign); intn_digit_t tmp_copy[INTN_MAX_RES_LEN]; memcpy(tmp_copy, m, m_len * sizeof(intn_digit_t)); diff --git a/src/libAtomVM/externalterm.c b/src/libAtomVM/externalterm.c index cbbf2b30a..d64c04f38 100644 --- a/src/libAtomVM/externalterm.c +++ b/src/libAtomVM/externalterm.c @@ -234,14 +234,12 @@ static int serialize_term(uint8_t *buf, term t, GlobalContext *glb) return SMALL_BIG_EXT_BASE_SIZE + num_bytes; } } else { - size_t intn_size = term_intn_size(t); - size_t digits_per_term = sizeof(term) / sizeof(intn_digit_t); - size_t bigint_len = intn_size * digits_per_term; - const intn_digit_t *bigint = (const intn_digit_t *) term_intn_data(t); + const intn_digit_t *bigint; + size_t bigint_len; + intn_integer_sign_t sign; + term_to_bigint(t, &bigint, &bigint_len, &sign); size_t num_bytes = intn_required_unsigned_integer_bytes(bigint, bigint_len); if (buf != NULL) { - intn_integer_sign_t sign = (intn_integer_sign_t) term_boxed_integer_sign(t); - buf[0] = SMALL_BIG_EXT; buf[1] = num_bytes; buf[2] = sign == IntNNegativeInteger ? 0x01 : 0x00; diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index dc546da7f..3fa9d8f00 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -2396,12 +2396,11 @@ static term integer_to_buf(Context *ctx, int argc, term argv[], char *tmp_buf, s } #endif default: { - size_t boxed_size = term_intn_size(value); - size_t digits_per_term = sizeof(term) / sizeof(intn_digit_t); - const intn_digit_t *intn_buf = (const intn_digit_t *) term_intn_data(value); - intn_integer_sign_t sign = (intn_integer_sign_t) term_boxed_integer_sign(value); - *int_buf - = intn_to_string(intn_buf, boxed_size * digits_per_term, sign, base, int_len); + const intn_digit_t *intn_buf; + size_t intn_buf_len; + intn_integer_sign_t sign; + term_to_bigint(value, &intn_buf, &intn_buf_len, &sign); + *int_buf = intn_to_string(intn_buf, intn_buf_len, sign, base, int_len); *needs_cleanup = true; } } diff --git a/src/libAtomVM/term.c b/src/libAtomVM/term.c index cbd73d0bf..e00ea1d1b 100644 --- a/src/libAtomVM/term.c +++ b/src/libAtomVM/term.c @@ -420,13 +420,12 @@ int term_funprint(PrinterFun *fun, term t, const GlobalContext *global) return fun->print(fun, AVM_INT64_FMT, term_unbox_int64(t)); #endif default: { - size_t digits_per_term = sizeof(term) / sizeof(intn_digit_t); - size_t boxed_size = term_intn_size(t); - const intn_digit_t *intn_data = (const intn_digit_t *) term_intn_data(t); - intn_integer_sign_t sign = (intn_integer_sign_t) term_boxed_integer_sign(t); + const intn_digit_t *intn_data; + size_t intn_data_len; + intn_integer_sign_t sign; + term_to_bigint(t, &intn_data, &intn_data_len, &sign); size_t unused_s_len; - char *s = intn_to_string( - intn_data, boxed_size * digits_per_term, sign, 10, &unused_s_len); + char *s = intn_to_string(intn_data, intn_data_len, sign, 10, &unused_s_len); if (IS_NULL_PTR(s)) { return -1; } @@ -1087,12 +1086,12 @@ avm_float_t term_conv_to_float(term t) return term_unbox_int64(t); #endif default: { - const intn_digit_t *num = (intn_digit_t *) term_intn_data(t); - size_t digits_per_term = (sizeof(term) / sizeof(intn_digit_t)); - size_t len = boxed_size * digits_per_term; - term_integer_sign_t t_sign = term_boxed_integer_sign(t); + const intn_digit_t *num; + size_t num_len; + intn_integer_sign_t num_sign; + term_to_bigint(t, &num, &num_len, &num_sign); - return intn_to_double(num, len, (intn_integer_sign_t) t_sign); + return intn_to_double(num, num_len, num_sign); } } } else { diff --git a/src/libAtomVM/term.h b/src/libAtomVM/term.h index e5d7d7c39..374f90656 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -1315,12 +1315,6 @@ static inline void *term_intn_data(term t) return (void *) (boxed_value + 1); } -static inline size_t term_intn_size(term t) -{ - const term *boxed_value = term_to_const_term_ptr(t); - return term_get_size_from_boxed_header(boxed_value[0]); -} - static inline void term_intn_to_term_size(size_t n, size_t *intn_data_size, size_t *rounded_num_len) { size_t bytes = n * sizeof(intn_digit_t); @@ -1340,6 +1334,85 @@ static inline void term_intn_to_term_size(size_t n, size_t *intn_data_size, size *rounded_num_len = rounded / sizeof(intn_digit_t); } +/** + * @brief Check if term is a multi-precision integer larger than \c int64_t + * + * Tests whether a term represents a boxed integer that requires multi-precision + * representation (i.e., larger than can fit in \c int64_t). These are integers + * that need more than \c INTN_INT64_LEN digits for their representation. + * + * In the current implementation, a bigint is defined as a boxed integer with + * size greater than: + * - \c BOXED_TERMS_REQUIRED_FOR_INT64 on 32-bit systems + * - \c BOXED_TERMS_REQUIRED_FOR_INT on 64-bit systems + * + * This effectively identifies integers that cannot be represented in the + * platform's native integer types and require multi-precision arithmetic, + * while avoiding confusion with regular boxed \c int64_t values that still + * fit within standard integer ranges. + * + * @param t Term to check + * @return true if term is a multi-precision integer, false otherwise + * + * @note Returns false for integers that fit in \c int64_t, even if boxed + * @note This is the correct check before calling \c term_to_bigint() + * + * @see term_to_bigint() to extract the multi-precision integer data + * @see term_is_boxed_integer() for checking any boxed integer + * @see term_is_any_integer() for checking all integer representations + */ +static inline bool term_is_bigint(term t) +{ + return term_is_boxed_integer(t) + && (term_boxed_size(t) > (INTN_INT64_LEN * sizeof(intn_digit_t)) / sizeof(term)); +} + +// intn doesn't depend on term +_Static_assert( + (int) TermPositiveInteger == (int) IntNPositiveInteger, "term/intn definition mismatch"); +_Static_assert( + (int) TermNegativeInteger == (int) IntNNegativeInteger, "term/intn definition mismatch"); + +/** + * @brief Extract multi-precision integer data from boxed term + * + * Extracts the raw multi-precision integer representation from a boxed + * integer term. This function provides direct access to the internal + * digit array without copying, returning a pointer to the data within + * the term structure. + * + * @param t Boxed integer term to extract from + * @param[out] bigint Pointer to the digit array within the term (borrowed reference) + * @param[out] bigint_len Number of digits in the integer + * @param[out] bigint_sign Sign of the integer + * + * @pre \c term_is_bigint(t) must be true + * @pre bigint != NULL + * @pre bigint_len != NULL + * @pre bigint_sign != NULL + * + * @warning Returned pointer is a borrowed reference into the term structure + * @warning Data becomes invalid if term is garbage collected or modified + * @warning Caller must not free the returned pointer + * + * @note The digit array may not be normalized (may have leading zeros) + * @note Length is calculated as boxed_size * (sizeof(term) / sizeof(intn_digit_t)) + * + * @see term_is_bigint() to check if term is a multi-precision integer + * @see term_boxed_integer_sign() to get the sign + */ +static inline void term_to_bigint( + term t, const intn_digit_t *bigint[], size_t *bigint_len, intn_integer_sign_t *bigint_sign) +{ + *bigint = (const intn_digit_t *) term_intn_data(t); + + const term *boxed_value = term_to_const_term_ptr(t); + size_t boxed_size = term_get_size_from_boxed_header(boxed_value[0]); + *bigint_len = boxed_size * (sizeof(term) / sizeof(intn_digit_t)); + + *bigint_sign = (intn_integer_sign_t) term_boxed_integer_sign(t); +} + static inline term term_from_catch_label(unsigned int module_index, unsigned int label) { return (term) ((module_index << 24) | (label << 6) | TERM_IMMED2_CATCH); From 9808df3a3d80fad7397f9396fec9e0a25d925f58 Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Sun, 26 Oct 2025 13:26:32 +0100 Subject: [PATCH 3/5] Add new term_initialize_bigint function Use `term_initialize_bigint` instead of `term_intn_data` + `intn_copy` Signed-off-by: Davide Bettio --- src/libAtomVM/bif.c | 3 +-- src/libAtomVM/externalterm.c | 3 +-- src/libAtomVM/jit.c | 4 +++- src/libAtomVM/nifs.c | 3 +-- src/libAtomVM/opcodesswitch.h | 3 +-- src/libAtomVM/term.h | 35 +++++++++++++++++++++++++++++++---- 6 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/libAtomVM/bif.c b/src/libAtomVM/bif.c index 9d2c89290..7fc440cd9 100644 --- a/src/libAtomVM/bif.c +++ b/src/libAtomVM/bif.c @@ -819,8 +819,7 @@ static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live, term bigres_term = term_create_uninitialized_intn( intn_data_size, (term_integer_sign_t) sign, &ctx->heap); - intn_digit_t *dest_buf = (void *) term_intn_data(bigres_term); - intn_copy(bigres, count, dest_buf, rounded_res_len); + term_initialize_bigint(bigres_term, bigres, count, rounded_res_len); return bigres_term; } else { diff --git a/src/libAtomVM/externalterm.c b/src/libAtomVM/externalterm.c index d64c04f38..d206872c6 100644 --- a/src/libAtomVM/externalterm.c +++ b/src/libAtomVM/externalterm.c @@ -597,8 +597,7 @@ static term parse_external_terms(const uint8_t *external_term_buf, size_t *eterm intn_integer_sign_t sign = is_negative ? IntNNegativeInteger : IntNPositiveInteger; term bigint_term = term_create_uninitialized_intn(intn_data_size, (term_integer_sign_t) sign, heap); - intn_digit_t *dest_buf = (void *) term_intn_data(bigint_term); - intn_copy(bigint, count, dest_buf, rounded_res_len); + term_initialize_bigint(bigint_term, bigint, count, rounded_res_len); return bigint_term; } diff --git a/src/libAtomVM/jit.c b/src/libAtomVM/jit.c index eb72e5e61..492597141 100644 --- a/src/libAtomVM/jit.c +++ b/src/libAtomVM/jit.c @@ -637,7 +637,9 @@ static term jit_alloc_big_integer_fragment( term bigint_term = term_create_uninitialized_intn(intn_data_size, (term_integer_sign_t) sign, &heap); - void *digits_mem = term_intn_data(bigint_term); + // Assumption: here we assume that bigints have standard boxed term layout + // This code might need to be updated when changing bigint memory layout + void *digits_mem = (void *) (term_to_const_term_ptr(bigint_term) + 1); // TODO: optimize: just initialize space that will not be used memset(digits_mem, 0, intn_data_size * sizeof(term)); diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 3fa9d8f00..d147f63a7 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -2065,8 +2065,7 @@ static term make_bigint(Context *ctx, const intn_digit_t bigres[], size_t bigres term bigres_term = term_create_uninitialized_intn(intn_data_size, (term_integer_sign_t) sign, &ctx->heap); - intn_digit_t *dest_buf = (void *) term_intn_data(bigres_term); - intn_copy(bigres, bigres_len, dest_buf, rounded_res_len); + term_initialize_bigint(bigres_term, bigres, bigres_len, rounded_res_len); return bigres_term; } diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index d3cb15d32..7ed2dc84d 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -1853,8 +1853,7 @@ static bool maybe_call_native(Context *ctx, atom_index_t module_name, atom_index term bigint_term = term_create_uninitialized_intn(intn_data_size, (term_integer_sign_t) sign, &heap); - intn_digit_t *dest_buf = (void *) term_intn_data(bigint_term); - intn_copy(bigint, count, dest_buf, rounded_res_len); + term_initialize_bigint(bigint_term, bigint, count, rounded_res_len); memory_heap_append_heap(&ctx->heap, &heap); diff --git a/src/libAtomVM/term.h b/src/libAtomVM/term.h index 374f90656..474a30c51 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -1309,10 +1309,37 @@ static inline term term_create_uninitialized_intn(size_t n, term_integer_sign_t return ((term) boxed_int) | TERM_PRIMARY_BOXED; } -static inline void *term_intn_data(term t) +/** + * @brief Initialize multi-precision integer data in a pre-allocated term + * + * Copies multi-precision integer digits into an already allocated boxed term, + * zero-extending to fill the entire allocated space. This function is used + * after creating an uninitialized bigint term to populate it with actual data. + * + * @param t Uninitialized bigint term (created with \c term_create_uninitialized_intn()) + * @param bigint Source digit array to copy + * @param bigint_len Number of digits in source array + * @param uninitialized_size Total size of destination buffer in digits + * + * @pre t must be a valid uninitialized bigint term + * @pre bigint != NULL + * @pre uninitialized_size must match the size allocated for the term + * + * @post Copies bigint_len digits from source to term + * @post Zero-fills remaining space from bigint_len to uninitialized_size + * + * @note This function does not set the sign - that should be done when creating the term + * @note The destination buffer size (uninitialized_size) is typically rounded up for alignment + * + * @see term_create_uninitialized_intn() to allocate the term before initialization + * @see intn_copy() which performs the actual copy and zero-extension + */ +static inline void term_initialize_bigint( + term t, const intn_digit_t *bigint, size_t bigint_len, size_t uninitialized_size) { const term *boxed_value = term_to_const_term_ptr(t); - return (void *) (boxed_value + 1); + intn_digit_t *dest_buf = (intn_digit_t *) (boxed_value + 1); + intn_copy(bigint, bigint_len, dest_buf, uninitialized_size); } static inline void term_intn_to_term_size(size_t n, size_t *intn_data_size, size_t *rounded_num_len) @@ -1404,9 +1431,9 @@ _Static_assert( static inline void term_to_bigint( term t, const intn_digit_t *bigint[], size_t *bigint_len, intn_integer_sign_t *bigint_sign) { - *bigint = (const intn_digit_t *) term_intn_data(t); - const term *boxed_value = term_to_const_term_ptr(t); + *bigint = (const intn_digit_t *) (boxed_value + 1); + size_t boxed_size = term_get_size_from_boxed_header(boxed_value[0]); *bigint_len = boxed_size * (sizeof(term) / sizeof(intn_digit_t)); From ba2c1a41818b433d1f327e766724640fdbd7964f Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Sun, 26 Oct 2025 14:44:47 +0100 Subject: [PATCH 4/5] Rename term_create_uninitialized_intn and term_intn_to_term_size `term_create_uninitialized_intn` -> `term_create_uninitialized_bigint` `term_intn_to_term_size` -> `term_bigint_size_requirements` Also add doxygen documentation. Signed-off-by: Davide Bettio --- src/libAtomVM/bif.c | 4 +- src/libAtomVM/externalterm.c | 6 +-- src/libAtomVM/jit.c | 4 +- src/libAtomVM/nifs.c | 4 +- src/libAtomVM/opcodesswitch.h | 4 +- src/libAtomVM/term.h | 77 +++++++++++++++++++++++++++++++---- 6 files changed, 79 insertions(+), 20 deletions(-) diff --git a/src/libAtomVM/bif.c b/src/libAtomVM/bif.c index 7fc440cd9..130336a26 100644 --- a/src/libAtomVM/bif.c +++ b/src/libAtomVM/bif.c @@ -809,7 +809,7 @@ static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live, if (!intn_fits_int64(bigres, count, sign)) { size_t intn_data_size; size_t rounded_res_len; - term_intn_to_term_size(count, &intn_data_size, &rounded_res_len); + term_bigint_size_requirements(count, &intn_data_size, &rounded_res_len); if (UNLIKELY(memory_ensure_free_with_roots( ctx, BOXED_INTN_SIZE(intn_data_size), live, ctx->x, MEMORY_CAN_SHRINK) @@ -817,7 +817,7 @@ static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live, RAISE_ERROR_BIF(fail_label, OUT_OF_MEMORY_ATOM); } - term bigres_term = term_create_uninitialized_intn( + term bigres_term = term_create_uninitialized_bigint( intn_data_size, (term_integer_sign_t) sign, &ctx->heap); term_initialize_bigint(bigres_term, bigres, count, rounded_res_len); diff --git a/src/libAtomVM/externalterm.c b/src/libAtomVM/externalterm.c index d206872c6..d25dea358 100644 --- a/src/libAtomVM/externalterm.c +++ b/src/libAtomVM/externalterm.c @@ -592,11 +592,11 @@ static term parse_external_terms(const uint8_t *external_term_buf, size_t *eterm size_t intn_data_size; size_t rounded_res_len; - term_intn_to_term_size(count, &intn_data_size, &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_intn(intn_data_size, (term_integer_sign_t) sign, heap); + = term_create_uninitialized_bigint(intn_data_size, (term_integer_sign_t) sign, heap); term_initialize_bigint(bigint_term, bigint, count, rounded_res_len); return bigint_term; @@ -1011,7 +1011,7 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini size_t required_digits = intn_required_digits_for_unsigned_integer(num_bytes); size_t data_size; size_t unused_rounded_len; - term_intn_to_term_size(required_digits, &data_size, &unused_rounded_len); + term_bigint_size_requirements(required_digits, &data_size, &unused_rounded_len); return BOXED_INTN_SIZE(data_size); } diff --git a/src/libAtomVM/jit.c b/src/libAtomVM/jit.c index 492597141..7ba733840 100644 --- a/src/libAtomVM/jit.c +++ b/src/libAtomVM/jit.c @@ -627,7 +627,7 @@ static term jit_alloc_big_integer_fragment( size_t intn_data_size; size_t rounded_res_len; - term_intn_to_term_size(digits_len, &intn_data_size, &rounded_res_len); + term_bigint_size_requirements(digits_len, &intn_data_size, &rounded_res_len); if (UNLIKELY(memory_init_heap(&heap, BOXED_INTN_SIZE(intn_data_size)) != MEMORY_GC_OK)) { ctx->x[0] = ERROR_ATOM; @@ -636,7 +636,7 @@ static term jit_alloc_big_integer_fragment( } term bigint_term - = term_create_uninitialized_intn(intn_data_size, (term_integer_sign_t) sign, &heap); + = term_create_uninitialized_bigint(intn_data_size, (term_integer_sign_t) sign, &heap); // Assumption: here we assume that bigints have standard boxed term layout // This code might need to be updated when changing bigint memory layout void *digits_mem = (void *) (term_to_const_term_ptr(bigint_term) + 1); diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index d147f63a7..963fbb960 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -2057,14 +2057,14 @@ static term make_bigint(Context *ctx, const intn_digit_t bigres[], size_t bigres { size_t intn_data_size; size_t rounded_res_len; - term_intn_to_term_size(bigres_len, &intn_data_size, &rounded_res_len); + term_bigint_size_requirements(bigres_len, &intn_data_size, &rounded_res_len); if (UNLIKELY(memory_ensure_free(ctx, BOXED_INTN_SIZE(intn_data_size)) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } term bigres_term - = term_create_uninitialized_intn(intn_data_size, (term_integer_sign_t) sign, &ctx->heap); + = term_create_uninitialized_bigint(intn_data_size, (term_integer_sign_t) sign, &ctx->heap); term_initialize_bigint(bigres_term, bigres, bigres_len, rounded_res_len); return bigres_term; diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index 7ed2dc84d..ffd7c6671 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -1840,7 +1840,7 @@ static bool maybe_call_native(Context *ctx, atom_index_t module_name, atom_index size_t intn_data_size; size_t rounded_res_len; - term_intn_to_term_size(count, &intn_data_size, &rounded_res_len); + term_bigint_size_requirements(count, &intn_data_size, &rounded_res_len); Heap heap; if (UNLIKELY( @@ -1852,7 +1852,7 @@ static bool maybe_call_native(Context *ctx, atom_index_t module_name, atom_index } term bigint_term - = term_create_uninitialized_intn(intn_data_size, (term_integer_sign_t) sign, &heap); + = term_create_uninitialized_bigint(intn_data_size, (term_integer_sign_t) sign, &heap); term_initialize_bigint(bigint_term, bigint, count, rounded_res_len); memory_heap_append_heap(&ctx->heap, &heap); diff --git a/src/libAtomVM/term.h b/src/libAtomVM/term.h index 474a30c51..a5929e758 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -1301,7 +1301,32 @@ static inline size_t term_boxed_integer_size(avm_int64_t value) } } -static inline term term_create_uninitialized_intn(size_t n, term_integer_sign_t sign, Heap *heap) +/** + * @brief Create an uninitialized bigint term with allocated storage + * + * Allocates heap space for a multi-precision integer term and sets up the + * boxed header with size and sign information. The digit data area is left + * uninitialized and must be filled using \c term_initialize_bigint(). + * + * @param n Size of data area in terms (not digits) + * @param sign Sign of the integer (\c TERM_INTEGER_POSITIVE or \c TERM_INTEGER_NEGATIVE) + * @param heap Heap to allocate from + * @return Newly created uninitialized bigint term + * + * @pre n must be > \c BOXED_TERMS_REQUIRED_FOR_INT64 to ensure bigint distinction + * @pre heap must have at least (1 + n) terms of free space + * + * @post Allocates 1 header term + n data terms on the heap + * @post Header contains size and sign information + * @post Data area is uninitialized and must be filled before use + * + * @note The size n is in terms, not \c intn_digit_t digits + * @note Use \c term_bigint_size_requirements() to calculate appropriate n value + * + * @see term_initialize_bigint() to fill the allocated data area + * @see term_bigint_size_requirements() to calculate required size + */ +static inline term term_create_uninitialized_bigint(size_t n, term_integer_sign_t sign, Heap *heap) { term *boxed_int = memory_heap_alloc(heap, 1 + n); boxed_int[0] = (n << 6) | TERM_BOXED_POSITIVE_INTEGER | sign; @@ -1316,7 +1341,7 @@ static inline term term_create_uninitialized_intn(size_t n, term_integer_sign_t * zero-extending to fill the entire allocated space. This function is used * after creating an uninitialized bigint term to populate it with actual data. * - * @param t Uninitialized bigint term (created with \c term_create_uninitialized_intn()) + * @param t Uninitialized bigint term (created with \c term_create_uninitialized_bigint()) * @param bigint Source digit array to copy * @param bigint_len Number of digits in source array * @param uninitialized_size Total size of destination buffer in digits @@ -1331,7 +1356,7 @@ static inline term term_create_uninitialized_intn(size_t n, term_integer_sign_t * @note This function does not set the sign - that should be done when creating the term * @note The destination buffer size (uninitialized_size) is typically rounded up for alignment * - * @see term_create_uninitialized_intn() to allocate the term before initialization + * @see term_create_uninitialized_bigint() to allocate the term before initialization * @see intn_copy() which performs the actual copy and zero-extension */ static inline void term_initialize_bigint( @@ -1342,18 +1367,52 @@ static inline void term_initialize_bigint( intn_copy(bigint, bigint_len, dest_buf, uninitialized_size); } -static inline void term_intn_to_term_size(size_t n, size_t *intn_data_size, size_t *rounded_num_len) +/** + * @brief Calculate term allocation size for multi-precision integer + * + * Converts the number of \c intn_digit_t digits needed for a bigint into the + * corresponding term allocation size and rounded digit count. Handles platform + * differences between 32-bit systems (where term = intn_digit_t) and 64-bit + * systems (where term = 2 × intn_digit_t), including alignment requirements. + * + * @param n Number of non-zero \c intn_digit_t digits in the integer + * @param[out] intn_data_size Number of terms needed for storage + * @param[out] rounded_num_len Rounded number of digits for zero-padding + * + * @pre n > 0 + * @pre intn_data_size != NULL + * @pre rounded_num_len != NULL + * + * @post *intn_data_size > \c BOXED_TERMS_REQUIRED_FOR_INT64 (ensures bigint distinction) + * @post *rounded_num_len >= n (includes padding for alignment) + * + * @note Forces minimum size > \c BOXED_TERMS_REQUIRED_FOR_INT64 to distinguish + * bigints from regular boxed int64 values (which use two's complement) + * @note Rounds up to 8-byte boundaries for alignment + * @note On 64-bit systems, 2 digits fit per term; on 32-bit systems, 1 digit per term + * + * @code + * // Example usage: + * size_t count = intn_count_digits(bigint, bigint_len); + * size_t intn_data_size, rounded_res_len; + * term_bigint_size_requirements(count, &intn_data_size, &rounded_res_len); + * term t = term_create_uninitialized_bigint(intn_data_size, sign, heap); + * term_initialize_bigint(t, bigint, count, rounded_res_len); + * @endcode + */ +static inline void term_bigint_size_requirements( + size_t n, size_t *intn_data_size, size_t *rounded_num_len) { size_t bytes = n * sizeof(intn_digit_t); size_t rounded = ((bytes + 7) >> 3) << 3; *intn_data_size = rounded / sizeof(term); if (*intn_data_size == BOXED_TERMS_REQUIRED_FOR_INT64) { - // we need to distinguish between "small" boxed integers, that are integers - // up to int64, and bigger integers. - // The real difference is that "small" boxed integers use 2-complement, - // real bigints not (and also endianess might differ). - // So we force real bigints to be > BOXED_TERMS_REQUIRED_FOR_INT64 terms + // We need to distinguish between "small" boxed integers (up to int64) + // and true bigints. Small boxed integers use two's complement + // representation, while bigints use sign-magnitude (and endianness + // might also differ). To ensure this distinction, we force bigints + // to use > BOXED_TERMS_REQUIRED_FOR_INT64 terms. *intn_data_size = BOXED_TERMS_REQUIRED_FOR_INT64 + 1; rounded = *intn_data_size * sizeof(term); } From 10ce1130d18897f4f4d94c6c712587c13a898ffa Mon Sep 17 00:00:00 2001 From: Davide Bettio Date: Sun, 26 Oct 2025 19:39:59 +0100 Subject: [PATCH 5/5] term: rename and clarify BOXED_INTN_SIZE macro Rename it to BOXED_BIGINT_HEAP_SIZE, and clarify that it must be always used, in order to have the suitable size for allocating space for the bigint term with its boxed header. Signed-off-by: Davide Bettio --- src/libAtomVM/bif.c | 2 +- src/libAtomVM/externalterm.c | 2 +- src/libAtomVM/jit.c | 2 +- src/libAtomVM/nifs.c | 2 +- src/libAtomVM/opcodesswitch.h | 2 +- src/libAtomVM/term.h | 29 ++++++++++++++++++++++++++--- 6 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/libAtomVM/bif.c b/src/libAtomVM/bif.c index 130336a26..0dd4ae0ab 100644 --- a/src/libAtomVM/bif.c +++ b/src/libAtomVM/bif.c @@ -812,7 +812,7 @@ static term make_bigint(Context *ctx, uint32_t fail_label, uint32_t live, term_bigint_size_requirements(count, &intn_data_size, &rounded_res_len); if (UNLIKELY(memory_ensure_free_with_roots( - ctx, BOXED_INTN_SIZE(intn_data_size), live, ctx->x, MEMORY_CAN_SHRINK) + ctx, BOXED_BIGINT_HEAP_SIZE(intn_data_size), live, ctx->x, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR_BIF(fail_label, OUT_OF_MEMORY_ATOM); } diff --git a/src/libAtomVM/externalterm.c b/src/libAtomVM/externalterm.c index d25dea358..cf34de81b 100644 --- a/src/libAtomVM/externalterm.c +++ b/src/libAtomVM/externalterm.c @@ -1012,7 +1012,7 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini size_t data_size; size_t unused_rounded_len; term_bigint_size_requirements(required_digits, &data_size, &unused_rounded_len); - return BOXED_INTN_SIZE(data_size); + return BOXED_BIGINT_HEAP_SIZE(data_size); } case ATOM_UTF8_EXT: diff --git a/src/libAtomVM/jit.c b/src/libAtomVM/jit.c index 7ba733840..775631217 100644 --- a/src/libAtomVM/jit.c +++ b/src/libAtomVM/jit.c @@ -629,7 +629,7 @@ static term jit_alloc_big_integer_fragment( size_t rounded_res_len; term_bigint_size_requirements(digits_len, &intn_data_size, &rounded_res_len); - if (UNLIKELY(memory_init_heap(&heap, BOXED_INTN_SIZE(intn_data_size)) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_init_heap(&heap, BOXED_BIGINT_HEAP_SIZE(intn_data_size)) != MEMORY_GC_OK)) { ctx->x[0] = ERROR_ATOM; ctx->x[1] = OUT_OF_MEMORY_ATOM; return term_invalid_term(); diff --git a/src/libAtomVM/nifs.c b/src/libAtomVM/nifs.c index 963fbb960..21e889585 100644 --- a/src/libAtomVM/nifs.c +++ b/src/libAtomVM/nifs.c @@ -2059,7 +2059,7 @@ static term make_bigint(Context *ctx, const intn_digit_t bigres[], size_t bigres size_t rounded_res_len; term_bigint_size_requirements(bigres_len, &intn_data_size, &rounded_res_len); - if (UNLIKELY(memory_ensure_free(ctx, BOXED_INTN_SIZE(intn_data_size)) != MEMORY_GC_OK)) { + if (UNLIKELY(memory_ensure_free(ctx, BOXED_BIGINT_HEAP_SIZE(intn_data_size)) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index ffd7c6671..3fa56031e 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -1844,7 +1844,7 @@ static bool maybe_call_native(Context *ctx, atom_index_t module_name, atom_index Heap heap; if (UNLIKELY( - memory_init_heap(&heap, BOXED_INTN_SIZE(intn_data_size)) != MEMORY_GC_OK)) { + memory_init_heap(&heap, BOXED_BIGINT_HEAP_SIZE(intn_data_size)) != MEMORY_GC_OK)) { ctx->x[0] = ERROR_ATOM; ctx->x[1] = OUT_OF_MEMORY_ATOM; *out_term = term_invalid_term(); diff --git a/src/libAtomVM/term.h b/src/libAtomVM/term.h index a5929e758..6e94d22b5 100644 --- a/src/libAtomVM/term.h +++ b/src/libAtomVM/term.h @@ -145,7 +145,6 @@ extern "C" { #define FUNCTION_REFERENCE_SIZE 4 #define BOXED_INT_SIZE (BOXED_TERMS_REQUIRED_FOR_INT + 1) #define BOXED_INT64_SIZE (BOXED_TERMS_REQUIRED_FOR_INT64 + 1) -#define BOXED_INTN_SIZE(term_size) ((term_size) + 1) #define BOXED_FUN_SIZE 3 #define FLOAT_SIZE (sizeof(float_term_t) / sizeof(term) + 1) #define REF_SIZE ((int) ((sizeof(uint64_t) / sizeof(term)) + 1)) @@ -188,6 +187,17 @@ extern "C" { #define TERM_BINARY_HEAP_SIZE(size) \ (TERM_BINARY_DATA_SIZE_IN_TERMS(size) + BINARY_HEADER_SIZE) +/** + * @def BOXED_BIGINT_HEAP_SIZE(term_size) + * @brief Calculate total heap allocation size for a bigint including header + * + * @param term_size Data size in terms (from \c term_bigint_size_requirements()) + * @return Total heap size needed including 1 term for boxed header + * + * @see term_bigint_size_requirements() which provides the term_size parameter + */ +#define BOXED_BIGINT_HEAP_SIZE(term_size) ((term_size) + 1) + #define TERM_DEBUG_ASSERT(...) #define TERM_FROM_ATOM_INDEX(atom_index) ((atom_index << TERM_IMMED2_TAG_SIZE) | TERM_IMMED2_ATOM) @@ -1308,7 +1318,7 @@ static inline size_t term_boxed_integer_size(avm_int64_t value) * boxed header with size and sign information. The digit data area is left * uninitialized and must be filled using \c term_initialize_bigint(). * - * @param n Size of data area in terms (not digits) + * @param n Size of data area in terms (not digits), from \c term_bigint_size_requirements() * @param sign Sign of the integer (\c TERM_INTEGER_POSITIVE or \c TERM_INTEGER_NEGATIVE) * @param heap Heap to allocate from * @return Newly created uninitialized bigint term @@ -1320,11 +1330,15 @@ static inline size_t term_boxed_integer_size(avm_int64_t value) * @post Header contains size and sign information * @post Data area is uninitialized and must be filled before use * + * @warning When ensuring heap space, use \c BOXED_BIGINT_HEAP_SIZE(n) to include + * the header term in the allocation size + * * @note The size n is in terms, not \c intn_digit_t digits * @note Use \c term_bigint_size_requirements() to calculate appropriate n value * * @see term_initialize_bigint() to fill the allocated data area * @see term_bigint_size_requirements() to calculate required size + * @see BOXED_BIGINT_HEAP_SIZE() to calculate total heap allocation including header */ static inline term term_create_uninitialized_bigint(size_t n, term_integer_sign_t sign, Heap *heap) { @@ -1376,7 +1390,7 @@ static inline void term_initialize_bigint( * systems (where term = 2 × intn_digit_t), including alignment requirements. * * @param n Number of non-zero \c intn_digit_t digits in the integer - * @param[out] intn_data_size Number of terms needed for storage + * @param[out] intn_data_size Number of terms needed for storage (excludes header) * @param[out] rounded_num_len Rounded number of digits for zero-padding * * @pre n > 0 @@ -1386,6 +1400,9 @@ static inline void term_initialize_bigint( * @post *intn_data_size > \c BOXED_TERMS_REQUIRED_FOR_INT64 (ensures bigint distinction) * @post *rounded_num_len >= n (includes padding for alignment) * + * @warning The returned intn_data_size does NOT include the boxed header term. + * Use \c BOXED_BIGINT_HEAP_SIZE(intn_data_size) when allocating heap space. + * * @note Forces minimum size > \c BOXED_TERMS_REQUIRED_FOR_INT64 to distinguish * bigints from regular boxed int64 values (which use two's complement) * @note Rounds up to 8-byte boundaries for alignment @@ -1396,9 +1413,15 @@ static inline void term_initialize_bigint( * size_t count = intn_count_digits(bigint, bigint_len); * size_t intn_data_size, rounded_res_len; * term_bigint_size_requirements(count, &intn_data_size, &rounded_res_len); + * + * // Ensure heap has space for data + header + * memory_ensure_free(ctx, BOXED_BIGINT_HEAP_SIZE(intn_data_size)); + * * term t = term_create_uninitialized_bigint(intn_data_size, sign, heap); * term_initialize_bigint(t, bigint, count, rounded_res_len); * @endcode + * + * @see BOXED_BIGINT_HEAP_SIZE() to include header in heap allocation */ static inline void term_bigint_size_requirements( size_t n, size_t *intn_data_size, size_t *rounded_num_len)