diff --git a/libs/jit/src/jit.erl b/libs/jit/src/jit.erl index 46be98ed9..d26a24da7 100644 --- a/libs/jit/src/jit.erl +++ b/libs/jit/src/jit.erl @@ -2563,6 +2563,42 @@ first_pass_bs_create_bin_compute_size( ) -> MSt1 = verify_is_integer(Src, Fail, MMod, MSt0), {MSt1, AccLiteralSize0 + 32, AccSizeReg0, State0}; +first_pass_bs_create_bin_compute_size( + float, Src, Size, _SegmentUnit, Fail, AccLiteralSize0, AccSizeReg0, MMod, MSt0, State0 +) -> + MSt1 = verify_is_number(Src, Fail, MMod, MSt0), + % Verify and get the float size (defaults to 64 if nil) + case Size of + ?TERM_NIL -> + {MSt1, AccLiteralSize0 + 64, AccSizeReg0, State0}; + _ -> + {MSt2, SizeValue} = term_to_int(Size, Fail, MMod, MSt1), + if + is_integer(SizeValue) -> + % If size is a literal, compiler would only allow 16/32/64. + {MSt2, AccLiteralSize0 + SizeValue, AccSizeReg0, State0}; + is_atom(SizeValue) -> + % Check if size is 16, 32, or 64 using 'and' of '!=' checks + MSt3 = cond_raise_badarg_or_jump_to_fail_label( + {'and', [ + {SizeValue, '!=', 16}, + {SizeValue, '!=', 32}, + {SizeValue, '!=', 64} + ]}, + Fail, + MMod, + MSt2 + ), + case AccSizeReg0 of + undefined -> + {MSt3, AccLiteralSize0, SizeValue, State0}; + _ -> + MSt4 = MMod:add(MSt3, AccSizeReg0, SizeValue), + MSt5 = MMod:free_native_registers(MSt4, [SizeValue]), + {MSt5, AccLiteralSize0, AccSizeReg0, State0} + end + end + end; first_pass_bs_create_bin_compute_size( integer, Src, Size, SegmentUnit, Fail, AccLiteralSize0, AccSizeReg0, MMod, MSt0, State0 ) -> @@ -2718,6 +2754,31 @@ first_pass_bs_create_bin_insert_value( MMod, MSt6, Offset, SizeValue, 1 ), {MSt7, NewOffset, CreatedBin}; +first_pass_bs_create_bin_insert_value( + float, Flags, Src, Size, _SegmentUnit, Fail, CreatedBin, Offset, MMod, MSt0 +) -> + % Src is a term (boxed float or integer) + {MSt1, SrcReg} = MMod:move_to_native_register(MSt0, Src), + {MSt2, FlagsValue} = decode_flags_list(Flags, MMod, MSt1), + % Get the float size (defaults to 64 if nil) + {MSt3, SizeValue} = + case Size of + ?TERM_NIL -> + {MSt2, 64}; + _ -> + term_to_int(Size, Fail, MMod, MSt2) + end, + % Call single primitive with size parameter + {MSt4, BoolResult} = MMod:call_primitive(MSt3, ?PRIM_BITSTRING_INSERT_FLOAT, [ + CreatedBin, Offset, {free, SrcReg}, SizeValue, {free, FlagsValue} + ]), + MSt5 = cond_raise_badarg_or_jump_to_fail_label( + {'(bool)', {free, BoolResult}, '==', false}, Fail, MMod, MSt4 + ), + {MSt6, NewOffset} = first_pass_bs_create_bin_insert_value_increment_offset( + MMod, MSt5, Offset, SizeValue, 1 + ), + {MSt6, NewOffset, CreatedBin}; first_pass_bs_create_bin_insert_value( string, _Flags, Src, Size, SegmentUnit, Fail, CreatedBin, Offset, MMod, MSt0 ) -> @@ -3313,6 +3374,13 @@ verify_is_any_integer(Arg1, Fail, MMod, MSt0) -> MSt0 ). +verify_is_number(Arg1, Fail, MMod, MSt0) -> + {MSt1, Reg} = MMod:copy_to_native_register(MSt0, Arg1), + {MSt2, IsNumber} = MMod:call_primitive(MSt1, ?PRIM_TERM_IS_NUMBER, [{free, Reg}]), + cond_raise_badarg_or_jump_to_fail_label( + {'(bool)', {free, IsNumber}, '==', false}, Fail, MMod, MSt2 + ). + %%----------------------------------------------------------------------------- %% @doc Test if Arg1 is a binary, jump to FailLabel if it isn't or raise %% badarg if FailLabel is 0 diff --git a/libs/jit/src/primitives.hrl b/libs/jit/src/primitives.hrl index d70e0f2df..af27583d4 100644 --- a/libs/jit/src/primitives.hrl +++ b/libs/jit/src/primitives.hrl @@ -94,6 +94,7 @@ -define(PRIM_STACKTRACE_BUILD, 71). -define(PRIM_TERM_REUSE_BINARY, 72). -define(PRIM_ALLOC_BIG_INTEGER_FRAGMENT, 73). +-define(PRIM_BITSTRING_INSERT_FLOAT, 74). % Parameters to ?PRIM_MEMORY_ENSURE_FREE_WITH_ROOTS % -define(MEMORY_NO_SHRINK, 0). diff --git a/src/libAtomVM/bitstring.c b/src/libAtomVM/bitstring.c index 0382086fa..eb3a1151b 100644 --- a/src/libAtomVM/bitstring.c +++ b/src/libAtomVM/bitstring.c @@ -330,6 +330,73 @@ void bitstring_copy_bits_incomplete_bytes(uint8_t *dst, size_t bits_offset, cons *dst = dest_byte; } +bool bitstring_extract_f16( + term src_bin, size_t offset, avm_int_t n, enum BitstringFlags bs_flags, avm_float_t *dst) +{ + unsigned long capacity = term_binary_size(src_bin); + if (8 * capacity - offset < (unsigned long) n) { + return false; + } + + if ((offset & 0x7) == 0) { + int byte_offset = offset >> 3; + const uint8_t *src = (const uint8_t *) term_binary_data(src_bin) + byte_offset; + + // Read 16-bit value + uint16_t f16_bits; + if (bs_flags & LittleEndianIntegerMask) { + f16_bits = READ_16LE_UNALIGNED(src); + } else { + f16_bits = READ_16_UNALIGNED(src); + } + + // Convert IEEE 754 half-precision to single-precision + uint32_t sign = (f16_bits >> 15) & 0x1; + uint32_t f16_exp = (f16_bits >> 10) & 0x1F; + uint32_t f16_mantissa = f16_bits & 0x3FF; + + uint32_t f32_bits; + if (f16_exp == 0) { + if (f16_mantissa == 0) { + // Zero + f32_bits = sign << 31; + } else { + // Subnormal number - normalize it + int e = -1; + uint32_t m = f16_mantissa; + do { + e++; + m <<= 1; + } while ((m & 0x400) == 0); + f16_mantissa = m & 0x3FF; + f16_exp = -e; + int32_t f32_exp = (int32_t) f16_exp + 127 - 15; + f32_bits = (sign << 31) | (f32_exp << 23) | (f16_mantissa << 13); + } + } else if (f16_exp == 0x1F) { + // Inf or NaN - not finite + return false; + } else { + // Normalized number + int32_t f32_exp = (int32_t) f16_exp + 127 - 15; + f32_bits = (sign << 31) | (f32_exp << 23) | (f16_mantissa << 13); + } + + union + { + uint32_t bits; + float fvalue; + } f32; + f32.bits = f32_bits; + + *dst = f32.fvalue; + return true; + } else { + // TODO: add support to floats not aligned to byte boundary + return false; + } +} + bool bitstring_extract_f32( term src_bin, size_t offset, avm_int_t n, enum BitstringFlags bs_flags, avm_float_t *dst) { @@ -423,3 +490,158 @@ intn_from_integer_options_t bitstring_flags_to_intn_opts(enum BitstringFlags bf) #endif return converted; } + +bool bitstring_insert_f16( + term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags) +{ + unsigned long capacity = term_binary_size(dst_bin); + if (8 * capacity - offset < 16) { + return false; + } + + if (!isfinite(value)) { + return false; + } + + if ((offset & 0x7) == 0) { + int byte_offset = offset >> 3; + uint8_t *dst = (uint8_t *) term_binary_data(dst_bin) + byte_offset; + + _Static_assert(sizeof(float) == 4, "Unsupported float size"); + + // Convert double to float first + union + { + uint32_t bits; + float fvalue; + } f32; + + f32.fvalue = (float) value; + uint32_t f32_bits = f32.bits; + + // Extract components from float (32-bit) + uint32_t sign = (f32_bits >> 31) & 0x1; + int32_t exp = ((f32_bits >> 23) & 0xFF) - 127; // Remove float bias + uint32_t mantissa = f32_bits & 0x7FFFFF; + + uint16_t f16_bits; + + // Handle special cases + if (exp > 15) { + // Overflow to infinity + f16_bits = (sign << 15) | 0x7C00; + } else if (exp < -14) { + // Underflow to zero or denormal + if (exp < -24) { + // Too small, round to zero + f16_bits = sign << 15; + } else { + // Denormal number + uint32_t denorm_mantissa = (mantissa | 0x800000) >> (-14 - exp); + f16_bits = (sign << 15) | (denorm_mantissa >> 13); + } + } else { + // Normal number + uint32_t f16_exp = exp + 15; // Add half-precision bias + // Round to nearest even (bit 12 is the rounding bit) + uint32_t f16_mantissa = (mantissa + 0x1000) >> 13; // Round and keep top 10 bits + // Handle mantissa overflow + if (f16_mantissa > 0x3FF) { + f16_mantissa = 0; + f16_exp++; + } + if (f16_exp > 30) { + // Overflow to infinity + f16_bits = (sign << 15) | 0x7C00; + } else { + f16_bits = (sign << 15) | (f16_exp << 10) | f16_mantissa; + } + } + + if (bs_flags & LittleEndianIntegerMask) { + WRITE_16LE_UNALIGNED(dst, f16_bits); + } else { + WRITE_16_UNALIGNED(dst, f16_bits); + } + return true; + } else { + // TODO: add support to floats not aligned to byte boundary + return false; + } +} + +bool bitstring_insert_f32( + term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags) +{ + unsigned long capacity = term_binary_size(dst_bin); + if (8 * capacity - offset < 32) { + return false; + } + + if (!isfinite(value)) { + return false; + } + + if ((offset & 0x7) == 0) { + int byte_offset = offset >> 3; + uint8_t *dst = (uint8_t *) term_binary_data(dst_bin) + byte_offset; + + _Static_assert(sizeof(float) == 4, "Unsupported float size"); + + union + { + uint32_t bits; + float fvalue; + } f32; + + f32.fvalue = (float) value; + + if (bs_flags & LittleEndianIntegerMask) { + WRITE_32LE_UNALIGNED(dst, f32.bits); + } else { + WRITE_32_UNALIGNED(dst, f32.bits); + } + return true; + } else { + // TODO: add support to floats not aligned to byte boundary + return false; + } +} + +bool bitstring_insert_f64( + term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags) +{ + unsigned long capacity = term_binary_size(dst_bin); + if (8 * capacity - offset < 64) { + return false; + } + + if (!isfinite(value)) { + return false; + } + + if ((offset & 0x7) == 0) { + int byte_offset = offset >> 3; + uint8_t *dst = (uint8_t *) term_binary_data(dst_bin) + byte_offset; + + _Static_assert(sizeof(double) == 8, "Unsupported double size"); + + union + { + uint64_t bits; + double fvalue; + } f64; + + f64.fvalue = value; + + if (bs_flags & LittleEndianIntegerMask) { + WRITE_64LE_UNALIGNED(dst, f64.bits); + } else { + WRITE_64_UNALIGNED(dst, f64.bits); + } + return true; + } else { + // TODO: add support to doubles not aligned to byte boundary + return false; + } +} diff --git a/src/libAtomVM/bitstring.h b/src/libAtomVM/bitstring.h index 8d23abf3e..f7c721a64 100644 --- a/src/libAtomVM/bitstring.h +++ b/src/libAtomVM/bitstring.h @@ -503,6 +503,8 @@ static inline void bitstring_copy_bits(uint8_t *dst, size_t bits_offset, const u } } +bool bitstring_extract_f16( + term src_bin, size_t offset, avm_int_t n, enum BitstringFlags bs_flags, avm_float_t *dst); bool bitstring_extract_f32( term src_bin, size_t offset, avm_int_t n, enum BitstringFlags bs_flags, avm_float_t *dst); bool bitstring_extract_f64( @@ -510,6 +512,13 @@ bool bitstring_extract_f64( intn_from_integer_options_t bitstring_flags_to_intn_opts(enum BitstringFlags bf); +bool bitstring_insert_f16( + term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags); +bool bitstring_insert_f32( + term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags); +bool bitstring_insert_f64( + term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags); + #ifdef __cplusplus } #endif diff --git a/src/libAtomVM/defaultatoms.def b/src/libAtomVM/defaultatoms.def index 35330fdec..4f82e5d5c 100644 --- a/src/libAtomVM/defaultatoms.def +++ b/src/libAtomVM/defaultatoms.def @@ -114,6 +114,7 @@ X(STRING_ATOM, "\x6", "string") X(UTF8_ATOM, "\x4", "utf8") X(UTF16_ATOM, "\x5", "utf16") X(UTF32_ATOM, "\x5", "utf32") +X(FLOAT_ATOM, "\x5", "float") X(COPY_ATOM, "\x4", "copy") X(REUSE_ATOM, "\x5", "reuse") diff --git a/src/libAtomVM/jit.c b/src/libAtomVM/jit.c index c9ee80d24..4e907e93c 100644 --- a/src/libAtomVM/jit.c +++ b/src/libAtomVM/jit.c @@ -1319,6 +1319,9 @@ static term jit_bitstring_extract_float(Context *ctx, term *bin_ptr, size_t offs avm_float_t value; bool status; switch (n) { + case 16: + status = bitstring_extract_f16(((term) bin_ptr) | TERM_PRIMARY_BOXED, offset, n, bs_flags, &value); + break; case 32: status = bitstring_extract_f32(((term) bin_ptr) | TERM_PRIMARY_BOXED, offset, n, bs_flags, &value); break; @@ -1435,6 +1438,18 @@ static bool jit_bitstring_insert_integer(term bin, size_t offset, term value, si return bitstring_insert_integer(bin, offset, int_value, n, flags); } +static bool jit_bitstring_insert_float(term bin, size_t offset, term value, size_t n, enum BitstringFlags flags) +{ + avm_float_t float_value = term_conv_to_float(value); + if (n == 16) { + return bitstring_insert_f16(bin, offset, float_value, flags); + } else if (n == 32) { + return bitstring_insert_f32(bin, offset, float_value, flags); + } else { + return bitstring_insert_f64(bin, offset, float_value, flags); + } +} + static void jit_bitstring_copy_module_str(Context *ctx, JITState *jit_state, term bin, size_t offset, int str_id, size_t len) { TRACE("jit_bitstring_copy_module_str: bin=%p offset=%d str_id=%d len=%d\n", (void *) bin, (int) offset, str_id, (int) len); @@ -1817,7 +1832,8 @@ const ModuleNativeInterface module_native_interface = { term_copy_map, jit_stacktrace_build, jit_term_reuse_binary, - jit_alloc_big_integer_fragment + jit_alloc_big_integer_fragment, + jit_bitstring_insert_float }; #endif diff --git a/src/libAtomVM/jit.h b/src/libAtomVM/jit.h index ec11860a8..dcd8d33ad 100644 --- a/src/libAtomVM/jit.h +++ b/src/libAtomVM/jit.h @@ -160,6 +160,7 @@ struct ModuleNativeInterface term (*stacktrace_build)(Context *ctx); term (*term_reuse_binary)(Context *ctx, term src, size_t len); term (*alloc_big_integer_fragment)(Context *ctx, size_t digits_len, term_integer_sign_t sign); + bool (*bitstring_insert_float)(term bin, size_t offset, term value, size_t n, enum BitstringFlags flags); }; extern const ModuleNativeInterface module_native_interface; diff --git a/src/libAtomVM/opcodes.h b/src/libAtomVM/opcodes.h index a58f08813..f2b90d1e8 100644 --- a/src/libAtomVM/opcodes.h +++ b/src/libAtomVM/opcodes.h @@ -86,6 +86,7 @@ #define OP_CALL_EXT_ONLY 78 #define OP_BS_PUT_INTEGER 89 #define OP_BS_PUT_BINARY 90 +#define OP_BS_PUT_FLOAT 91 #define OP_BS_PUT_STRING 92 #define OP_FCLEARERROR 94 #define OP_FCHECKERROR 95 diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index d1fdeacb4..cdc3799db 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -4839,6 +4839,70 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) break; } +#if MINIMUM_OTP_COMPILER_VERSION <= 24 + case OP_BS_PUT_FLOAT: { + uint32_t fail; + DECODE_LABEL(fail, pc) + term size; + DECODE_COMPACT_TERM(size, pc) + uint32_t unit; + DECODE_LITERAL(unit, pc); + uint32_t flags_value; + DECODE_LITERAL(flags_value, pc) + term src; + DECODE_COMPACT_TERM(src, pc); + + #ifdef IMPL_CODE_LOADER + TRACE("bs_put_float/5\n"); + #endif + + #ifdef IMPL_EXECUTE_LOOP + avm_float_t float_value; + if (term_is_float(src)) { + float_value = term_to_float(src); + } else if (term_is_any_integer(src)) { + float_value = (avm_float_t) term_maybe_unbox_int64(src); + } else { + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); + } + } + + avm_int_t signed_size_value = 64; + if (size != term_nil()) { + VERIFY_IS_INTEGER(size, "bs_create_bin/6", fail); + signed_size_value = term_to_int(size); + if (UNLIKELY(signed_size_value != 16 && signed_size_value != 32 && signed_size_value != 64)) { + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); + } + } + } + + bool result; + if (signed_size_value == 16) { + result = bitstring_insert_f16(ctx->bs, ctx->bs_offset, float_value, flags_value); + } else if (signed_size_value == 32) { + result = bitstring_insert_f32(ctx->bs, ctx->bs_offset, float_value, flags_value); + } else { + result = bitstring_insert_f64(ctx->bs, ctx->bs_offset, float_value, flags_value); + } + + if (UNLIKELY(!result)) { + TRACE("bs_put_float: Failed to insert float into binary\n"); + RAISE_ERROR(BADARG_ATOM); + } + + ctx->bs_offset += signed_size_value * unit; + #endif + break; + } +#endif + case OP_BS_PUT_STRING: { uint32_t size; DECODE_LITERAL(size, pc); @@ -5389,6 +5453,9 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) avm_int_t bs_offset = term_get_match_state_offset(src); bool status; switch (size_val) { + case 16: + status = bitstring_extract_f16(bs_bin, bs_offset, increment, flags_value, &value); + break; case 32: status = bitstring_extract_f32(bs_bin, bs_offset, increment, flags_value, &value); break; @@ -6914,6 +6981,31 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) segment_size = signed_size_value; break; } + case FLOAT_ATOM: { + if (!term_is_number(src)) { + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); + } + } + // size is optional for floats, defaults to 64 + avm_int_t signed_size_value = 64; + if (size != term_nil()) { + VERIFY_IS_INTEGER(size, "bs_create_bin/6", fail); + signed_size_value = term_to_int(size); + if (UNLIKELY(signed_size_value != 16 && signed_size_value != 32 && signed_size_value != 64)) { + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); + } + } + } + segment_size = signed_size_value; + segment_unit = 1; + break; + } case STRING_ATOM: { VERIFY_IS_INTEGER(size, "bs_create_bin/6", fail); avm_int_t signed_size_value = term_to_int(size); @@ -7011,6 +7103,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) case UTF16_ATOM: case UTF32_ATOM: case INTEGER_ATOM: + case FLOAT_ATOM: DECODE_FLAGS_LIST(flags_value, flags, opcode); break; default: @@ -7034,6 +7127,13 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) case STRING_ATOM: size_value = (size_t) term_to_int(size); break; + case FLOAT_ATOM: + if (size != term_nil()) { + size_value = (size_t) term_to_int(size); + } else { + size_value = 64; + } + break; default: break; } @@ -7076,6 +7176,38 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) segment_size = size_value; break; } + case FLOAT_ATOM: { + avm_float_t float_value; + if (term_is_float(src)) { + float_value = term_to_float(src); + } else if (term_is_any_integer(src)) { + float_value = (avm_float_t) term_maybe_unbox_int64(src); + } else { + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); + } + } + bool result; + if (size_value == 16) { + result = bitstring_insert_f16(t, offset, float_value, flags_value); + } else if (size_value == 32) { + result = bitstring_insert_f32(t, offset, float_value, flags_value); + } else { + result = bitstring_insert_f64(t, offset, float_value, flags_value); + } + if (UNLIKELY(!result)) { + TRACE("bs_create_bin/6: Failed to insert float into binary\n"); + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); + } + } + segment_size = size_value; + break; + } case STRING_ATOM: { uint8_t *dst = (uint8_t *) term_binary_data(t); size_t remaining = 0; diff --git a/tests/erlang_tests/test_bs.erl b/tests/erlang_tests/test_bs.erl index 460e8774e..333d64449 100644 --- a/tests/erlang_tests/test_bs.erl +++ b/tests/erlang_tests/test_bs.erl @@ -99,6 +99,8 @@ start() -> ok = test_bs_skip_bits2_little(), + ok = test_float(), + 0. test_pack_small_ints({A, B, C}, Expect) -> @@ -532,6 +534,72 @@ test_bs_match_string_select() -> test_bs_skip_bits2_little() -> ok = check_x86_64_jt(id(<<16#e9, 0:32>>)). +test_float() -> + Pi = id(3.14), + <<64, 9, 30, 184, 81, 235, 133, 31, 3, 14>> = <>, + <<64, 9, 30, 184, 81, 235, 133, 31, 3, 14>> = <>, + <<31, 133, 235, 81, 184, 30, 9, 64, 3, 14>> = <>, + <<_:64, 3, 14>> = <>, + <<64, 72, 245, 195, 3, 14>> = <>, + <<195, 245, 72, 64, 3, 14>> = <>, + + <> = id(<<64, 9, 30, 184, 81, 235, 133, 31, 3, 14>>), + <> = id(<<31, 133, 235, 81, 184, 30, 9, 64, 3, 14>>), + <> = id(<<64, 72, 245, 195, 3, 14>>), + <> = id(<<195, 245, 72, 64, 3, 14>>), + true = abs(PiS - Pi) < 0.0001, + + % Test integer to float conversion + Int2 = id(2), + IntNeg2 = id(-2), + Int32 = id(32), + <<64, 0, 0, 0, 0, 0, 0, 0>> = <>, + <<192, 0, 0, 0, 0, 0, 0, 0>> = <>, + <<66, 0, 0, 0>> = <>, + + % 16-bit floats are supported in OTP 24+ and AtomVM + Has16BitFloats = + case erlang:system_info(machine) of + "BEAM" -> + erlang:system_info(otp_release) >= "24"; + "ATOM" -> + true + end, + if + Has16BitFloats -> + % Test that 16-bit floats work + Pi16 = id(3.14), + <<66, 72>> = <>, + <<66, 72>> = <>, + <<72, 66>> = <>, + <> = <<66, 72, 3, 14>>, + <> = <<72, 66, 3, 14>>, + true = abs(Pi16B - Pi16) < 0.001, + ok; + true -> + ok + end, + + ok = test_create_with_invalid_float_value(), + ok = test_create_with_invalid_float_size(), + ok. + +test_create_with_invalid_float_value() -> + ok = expect_error(fun() -> create_float_binary(foo, id(64)) end, badarg), + ok = expect_error(fun() -> create_float_binary([1, 2, 3], id(32)) end, badarg), + ok = expect_error(fun() -> create_float_binary(<<"binary">>, id(64)) end, badarg), + ok. + +test_create_with_invalid_float_size() -> + % These sizes are invalid in both BEAM and AtomVM + ok = expect_error(fun() -> create_float_binary(3.14, id(8)) end, badarg), + ok = expect_error(fun() -> create_float_binary(3.14, id(128)) end, badarg), + ok = expect_error(fun() -> create_float_binary(3.14, id(foo)) end, badarg), + ok. + +create_float_binary(Value, Size) -> + <>. + check_x86_64_jt(<<>>) -> ok; check_x86_64_jt(<<16#e9, _Offset:32/little, Tail/binary>>) -> check_x86_64_jt(Tail); check_x86_64_jt(Bin) -> {unexpected, Bin}.