diff --git a/src/libAtomVM/bitstring.c b/src/libAtomVM/bitstring.c index b74c36531..0382086fa 100644 --- a/src/libAtomVM/bitstring.c +++ b/src/libAtomVM/bitstring.c @@ -406,3 +406,20 @@ bool bitstring_extract_f64( return false; } } + +intn_from_integer_options_t bitstring_flags_to_intn_opts(enum BitstringFlags bf) +{ + intn_from_integer_options_t converted = IntnUnsignedBigEndian; + if (bf & LittleEndianInteger) { + converted |= IntnLittleEndian; + } + if (bf & SignedInteger) { + converted |= IntnSigned; + } +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + if (bf & NativeEndianInteger) { + converted |= IntnLittleEndian; + } +#endif + return converted; +} diff --git a/src/libAtomVM/bitstring.h b/src/libAtomVM/bitstring.h index 0fe3aece1..b566cfdfd 100644 --- a/src/libAtomVM/bitstring.h +++ b/src/libAtomVM/bitstring.h @@ -22,6 +22,7 @@ #ifndef _BITSTRING_H_ #define _BITSTRING_H_ +#include "intn.h" #include "term.h" #include "unicode.h" @@ -528,6 +529,8 @@ bool bitstring_extract_f32( bool bitstring_extract_f64( term src_bin, size_t offset, avm_int_t n, enum BitstringFlags bs_flags, avm_float_t *dst); +intn_from_integer_options_t bitstring_flags_to_intn_opts(enum BitstringFlags bf); + #ifdef __cplusplus } #endif diff --git a/src/libAtomVM/intn.c b/src/libAtomVM/intn.c index 553da50e8..9c331919c 100644 --- a/src/libAtomVM/intn.c +++ b/src/libAtomVM/intn.c @@ -1182,6 +1182,8 @@ int intn_from_integer_bytes(const uint8_t in[], size_t in_size, intn_from_intege sign = IntNNegativeInteger; } *out_sign = sign; + } else if (out_sign) { + *out_sign = IntNPositiveInteger; } memset(out, filler, INTN_MAX_RES_LEN * sizeof(intn_digit_t)); diff --git a/src/libAtomVM/jit.c b/src/libAtomVM/jit.c index 775631217..31228c1e8 100644 --- a/src/libAtomVM/jit.c +++ b/src/libAtomVM/jit.c @@ -1244,19 +1244,64 @@ static term jit_term_alloc_bin_match_state(Context *ctx, term src, int slots) return term_alloc_bin_match_state(src, slots, &ctx->heap); } -static term jit_bitstring_extract_integer(Context *ctx, JITState *jit_state, term *bin_ptr, size_t offset, int n, int bs_flags) +static term extract_bigint(Context *ctx, JITState *jit_state, const uint8_t *bytes, + size_t bytes_size, intn_from_integer_options_t opts) { - TRACE("jit_bitstring_extract_integer: bin_ptr=%p offset=%d n=%d bs_flags=%d\n", (void *) bin_ptr, (int) offset, n, bs_flags); - union maybe_unsigned_int64 value; - bool status = bitstring_extract_integer(((term) bin_ptr) | TERM_PRIMARY_BOXED, offset, n, bs_flags, &value); - if (UNLIKELY(!status)) { + intn_integer_sign_t sign; + intn_digit_t bigint[INTN_MAX_RES_LEN]; + int count = intn_from_integer_bytes(bytes, bytes_size, opts, bigint, &sign); + // count will be always >= 0, caller ensures that bits <= INTN_MAX_UNSIGNED_BITS_SIZE + + size_t intn_data_size; + size_t rounded_res_len; + term_bigint_size_requirements(count, &intn_data_size, &rounded_res_len); + + Heap heap; + if (UNLIKELY(memory_init_heap(&heap, BOXED_BIGINT_HEAP_SIZE(intn_data_size)) != MEMORY_GC_OK)) { + set_error(ctx, jit_state, 0, OUT_OF_MEMORY_ATOM); return FALSE_ATOM; } - term t = maybe_alloc_boxed_integer_fragment(ctx, value.s); - if (UNLIKELY(term_is_invalid_term(t))) { - set_error(ctx, jit_state, 0, OUT_OF_MEMORY_ATOM); + + 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); + + memory_heap_append_heap(&ctx->heap, &heap); + + return bigint_term; +} + +static term jit_bitstring_extract_integer( + Context *ctx, JITState *jit_state, term *bin_ptr, size_t offset, int n, int bs_flags) +{ + TRACE("jit_bitstring_extract_integer: bin_ptr=%p offset=%d n=%d bs_flags=%d\n", + (void *) bin_ptr, (int) offset, n, bs_flags); + if (n <= 64) { + union maybe_unsigned_int64 value; + bool status = bitstring_extract_integer( + ((term) bin_ptr) | TERM_PRIMARY_BOXED, offset, n, bs_flags, &value); + if (UNLIKELY(!status)) { + return FALSE_ATOM; + } + term t = maybe_alloc_boxed_integer_fragment(ctx, value.s); + if (UNLIKELY(term_is_invalid_term(t))) { + set_error(ctx, jit_state, 0, OUT_OF_MEMORY_ATOM); + } + return t; + } else if ((offset % 8 == 0) && (n % 8 == 0) && (n <= INTN_MAX_UNSIGNED_BITS_SIZE)) { + term bs_bin = ((term) bin_ptr) | TERM_PRIMARY_BOXED; + unsigned long capacity = term_binary_size(bs_bin); + if (8 * capacity - offset < (unsigned long) n) { + return FALSE_ATOM; + } + size_t byte_offset = offset / 8; + const uint8_t *int_bytes = (const uint8_t *) term_binary_data(bs_bin); + + return extract_bigint( + ctx, jit_state, int_bytes + byte_offset, n / 8, bitstring_flags_to_intn_opts(bs_flags)); + } else { + return FALSE_ATOM; } - return t; } static term jit_bitstring_extract_float(Context *ctx, term *bin_ptr, size_t offset, int n, int bs_flags) diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index 3fa56031e..d02c03097 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -1814,6 +1814,40 @@ static bool maybe_call_native(Context *ctx, atom_index_t module_name, atom_index #endif #ifndef AVM_NO_EMU + static term extract_nbits_integer(Context *ctx, const uint8_t *bytes, size_t bytes_size, intn_from_integer_options_t opts) + { + intn_integer_sign_t sign; + intn_digit_t bigint[INTN_MAX_RES_LEN]; + int count = intn_from_integer_bytes(bytes, bytes_size, opts, bigint, &sign); + if (UNLIKELY(count < 0)) { + // this is likely unreachable, compiler seem to generate an external term + // and to encode this as SMALL_BIG_EXT, so I don't think this code is executed + ctx->x[0] = ERROR_ATOM; + ctx->x[1] = OVERFLOW_ATOM; + return term_invalid_term(); + } + + size_t intn_data_size; + size_t rounded_res_len; + term_bigint_size_requirements(count, &intn_data_size, &rounded_res_len); + + Heap heap; + 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(); + } + + 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); + + memory_heap_append_heap(&ctx->heap, &heap); + + return bigint_term; + } + static size_t decode_nbits_integer(Context *ctx, const uint8_t *encoded, term *out_term) { const uint8_t *new_encoded = encoded; @@ -1826,41 +1860,9 @@ static bool maybe_call_native(Context *ctx, atom_index_t module_name, atom_index len += 9; if (out_term) { - intn_integer_sign_t sign; - intn_digit_t bigint[INTN_MAX_RES_LEN]; - int count = intn_from_integer_bytes(new_encoded, len, IntnSigned, bigint, &sign); - if (UNLIKELY(count < 0)) { - // this is likely unreachable, compiler seem to generate an external term - // and to encode this as SMALL_BIG_EXT, so I don't think this code is executed - ctx->x[0] = ERROR_ATOM; - ctx->x[1] = OVERFLOW_ATOM; - *out_term = term_invalid_term(); - goto return_size; - } - - size_t intn_data_size; - size_t rounded_res_len; - term_bigint_size_requirements(count, &intn_data_size, &rounded_res_len); - - Heap heap; - 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; - *out_term = term_invalid_term(); - goto return_size; - } - - 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); - - memory_heap_append_heap(&ctx->heap, &heap); - - *out_term = bigint_term; + *out_term = extract_nbits_integer(ctx, new_encoded, len, IntnSigned); } - return_size: return (new_encoded - encoded) + len; } #endif @@ -5298,25 +5300,44 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) union maybe_unsigned_int64 value; term bs_bin = term_get_match_state_binary(src); avm_int_t bs_offset = term_get_match_state_offset(src); - bool status = bitstring_extract_integer(bs_bin, bs_offset, increment, flags_value, &value); - if (UNLIKELY(!status)) { - TRACE("bs_get_integer2: error extracting integer.\n"); - JUMP_TO_ADDRESS(mod->labels[fail]); - } else { - term_set_match_state_offset(src, bs_offset + increment); + term t; + if (increment <= 64) { + bool status = bitstring_extract_integer(bs_bin, bs_offset, increment, flags_value, &value); + if (UNLIKELY(!status)) { + TRACE("bs_get_integer2: error extracting integer.\n"); + JUMP_TO_ADDRESS(mod->labels[fail]); + } else { + term_set_match_state_offset(src, bs_offset + increment); - term t = maybe_alloc_boxed_integer_fragment(ctx, value.s); - if (UNLIKELY(term_is_invalid_term(t))) { + t = maybe_alloc_boxed_integer_fragment(ctx, value.s); + if (UNLIKELY(term_is_invalid_term(t))) { + HANDLE_ERROR(); + } + } + } else if ((bs_offset % 8 == 0) && (increment % 8 == 0) && (increment <= INTN_MAX_UNSIGNED_BITS_SIZE)) { + unsigned long capacity = term_binary_size(bs_bin); + if (8 * capacity - bs_offset < (unsigned long) increment) { + JUMP_TO_ADDRESS(mod->labels[fail]); + } + size_t byte_offset = bs_offset / 8; + const uint8_t *int_bytes = (const uint8_t *) term_binary_data(bs_bin); + + t = extract_nbits_integer(ctx, int_bytes + byte_offset, increment / 8, + bitstring_flags_to_intn_opts(flags_value)); + term_set_match_state_offset(src, bs_offset + increment); + if (term_is_invalid_term(t)) { HANDLE_ERROR(); } + } else { + JUMP_TO_ADDRESS(mod->labels[fail]); + } #endif DEST_REGISTER(dreg); DECODE_DEST_REGISTER(dreg, pc); #ifdef IMPL_EXECUTE_LOOP - WRITE_REGISTER(dreg, t); - } + WRITE_REGISTER(dreg, t); #endif break; } @@ -7273,15 +7294,35 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) avm_int_t size_val = term_to_int(size); avm_int_t increment = size_val * unit; union maybe_unsigned_int64 value; - bool status = bitstring_extract_integer(bs_bin, bs_offset, increment, flags_value, &value); - if (UNLIKELY(!status)) { - TRACE("bs_match/3: error extracting integer.\n"); + term t; + if (increment <= 64) { + bool status = bitstring_extract_integer(bs_bin, bs_offset, increment, flags_value, &value); + if (UNLIKELY(!status)) { + TRACE("bs_match/3: error extracting integer.\n"); + goto bs_match_jump_to_fail; + } + //FIXME: handling of 64-bit unsigned integers is not reliable + t = maybe_alloc_boxed_integer_fragment(ctx, value.s); + if (UNLIKELY(term_is_invalid_term(t))) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + } else if ((bs_offset % 8 == 0) && (increment % 8 == 0) && (increment <= INTN_MAX_UNSIGNED_BITS_SIZE)) { + unsigned long capacity = term_binary_size(bs_bin); + if (8 * capacity - bs_offset < (unsigned long) increment) { + goto bs_match_jump_to_fail; + } + size_t byte_offset = bs_offset / 8; + const uint8_t *int_bytes + = (const uint8_t *) term_binary_data(bs_bin); + + t = extract_nbits_integer(ctx, int_bytes + byte_offset, + increment / 8, bitstring_flags_to_intn_opts(flags_value)); + if (term_is_invalid_term(t)) { + HANDLE_ERROR(); + } + } else { goto bs_match_jump_to_fail; } - term t = maybe_alloc_boxed_integer_fragment(ctx, value.s); - if (UNLIKELY(term_is_invalid_term(t))) { - RAISE_ERROR(OUT_OF_MEMORY_ATOM); - } #endif DEST_REGISTER(dreg); DECODE_DEST_REGISTER(dreg, pc); @@ -7388,6 +7429,10 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) DECODE_LITERAL(pattern_value, pc); j++; #ifdef IMPL_EXECUTE_LOOP + if (size > 64) { + // TODO: implement support for big integers also here + RAISE_ERROR(BADARG_ATOM); + } union maybe_unsigned_int64 matched_value; bool status = bitstring_extract_integer(bs_bin, bs_offset, size, 0, &matched_value); if (UNLIKELY(!status)) { diff --git a/tests/erlang_tests/bigint.erl b/tests/erlang_tests/bigint.erl index 30dd4dca9..6069269fa 100644 --- a/tests/erlang_tests/bigint.erl +++ b/tests/erlang_tests/bigint.erl @@ -72,6 +72,7 @@ start() -> test_is_number() + test_gt_lt_guards() + to_external_term() + + test_pattern_match() + test_band() + test_bxor() + test_bor() + @@ -2138,6 +2139,44 @@ to_external_term() -> 0. +test_pattern_match() -> + <> = ?MODULE:id(<<23, 4, 222, 66, 172, 197, 113, 183, 80>>), + <<"50B771C5AC42DE0417">> = erlang:integer_to_binary(?MODULE:id(Int72), 16), + <> = ?MODULE:id( + <<165, 63, 196, 58, 33, 96, 209, 59, 244, 213>> + ), + <<"-2A0BC42E9FDEC53BC05B">> = erlang:integer_to_binary(?MODULE:id(Int80), 16), + <> = ?MODULE:id( + <<0, 242, 138, 221, 68, 111, 58, 120, 145, 135, 164, 56, 164, 12, 205>> + ), + <<"F28ADD446F3A789187A438A40CCD">> = erlang:integer_to_binary(?MODULE:id(Int120), 16), + <> = ?MODULE:id( + <<202, 196, 64, 150, 63, 238, 50, 47, 214, 81, 247, 55, 151, 242, 169, 106, 162, 211, 73, + 155, 211, 85, 164, 237, 153, 138, 191, 77, 87, 183, 204, 111>> + ), + <<"CAC440963FEE322FD651F73797F2A96AA2D3499BD355A4ED998ABF4D57B7CC6F">> = erlang:integer_to_binary( + ?MODULE:id(Int256), 16 + ), + + <<"foo", Int128:128/unsigned-little-integer, Bar/binary>> = ?MODULE:id( + <<102, 111, 111, 183, 226, 155, 102, 249, 246, 168, 101, 53, 36, 21, 10, 133, 223, 231, 10, + 98, 97, 114>> + ), + <<"AE7DF850A15243565A8F6F9669BE2B7">> = erlang:integer_to_binary(?MODULE:id(Int128), 16), + <<"bar">> = ?MODULE:id(Bar), + + ok = + case + ?MODULE:id( + <<102, 111, 111, 183, 226, 155, 102, 249, 246, 168, 101, 53, 36, 21, 10, 133, 223, + 231>> + ) + of + <<"foo", _I128:128/unsigned-little-integer, Bar/binary>> -> error; + _ -> ok + end, + 0. + test_band() -> MaxPatternBin = <<"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF">>, MaxPattern = erlang:binary_to_integer(?MODULE:id(MaxPatternBin), 16),