Skip to content

Commit b7241e2

Browse files
committed
Improve support for floats in bitstrings
Add support for IEEE 754 half-precision, single-precision and double precision encoding and for IEEE 754 half-precision decoding. Also add support for OP_BS_PUT_FLOAT which is emitted by OTP <= 24. Signed-off-by: Paul Guyot <[email protected]>
1 parent 593b3ee commit b7241e2

File tree

10 files changed

+520
-1
lines changed

10 files changed

+520
-1
lines changed

libs/jit/src/jit.erl

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2563,6 +2563,42 @@ first_pass_bs_create_bin_compute_size(
25632563
) ->
25642564
MSt1 = verify_is_integer(Src, Fail, MMod, MSt0),
25652565
{MSt1, AccLiteralSize0 + 32, AccSizeReg0, State0};
2566+
first_pass_bs_create_bin_compute_size(
2567+
float, Src, Size, _SegmentUnit, Fail, AccLiteralSize0, AccSizeReg0, MMod, MSt0, State0
2568+
) ->
2569+
MSt1 = verify_is_number(Src, Fail, MMod, MSt0),
2570+
% Verify and get the float size (defaults to 64 if nil)
2571+
case Size of
2572+
?TERM_NIL ->
2573+
{MSt1, AccLiteralSize0 + 64, AccSizeReg0, State0};
2574+
_ ->
2575+
{MSt2, SizeValue} = term_to_int(Size, Fail, MMod, MSt1),
2576+
if
2577+
is_integer(SizeValue) ->
2578+
% If size is a literal, compiler would only allow 16/32/64.
2579+
{MSt2, AccLiteralSize0 + SizeValue, AccSizeReg0, State0};
2580+
is_atom(SizeValue) ->
2581+
% Check if size is 16, 32, or 64 using 'and' of '!=' checks
2582+
MSt3 = cond_raise_badarg_or_jump_to_fail_label(
2583+
{'and', [
2584+
{SizeValue, '!=', 16},
2585+
{SizeValue, '!=', 32},
2586+
{SizeValue, '!=', 64}
2587+
]},
2588+
Fail,
2589+
MMod,
2590+
MSt2
2591+
),
2592+
case AccSizeReg0 of
2593+
undefined ->
2594+
{MSt3, AccLiteralSize0, SizeValue, State0};
2595+
_ ->
2596+
MSt4 = MMod:add(MSt3, AccSizeReg0, SizeValue),
2597+
MSt5 = MMod:free_native_registers(MSt4, [SizeValue]),
2598+
{MSt5, AccLiteralSize0, AccSizeReg0, State0}
2599+
end
2600+
end
2601+
end;
25662602
first_pass_bs_create_bin_compute_size(
25672603
integer, Src, Size, SegmentUnit, Fail, AccLiteralSize0, AccSizeReg0, MMod, MSt0, State0
25682604
) ->
@@ -2718,6 +2754,31 @@ first_pass_bs_create_bin_insert_value(
27182754
MMod, MSt6, Offset, SizeValue, 1
27192755
),
27202756
{MSt7, NewOffset, CreatedBin};
2757+
first_pass_bs_create_bin_insert_value(
2758+
float, Flags, Src, Size, _SegmentUnit, Fail, CreatedBin, Offset, MMod, MSt0
2759+
) ->
2760+
% Src is a term (boxed float or integer)
2761+
{MSt1, SrcReg} = MMod:move_to_native_register(MSt0, Src),
2762+
{MSt2, FlagsValue} = decode_flags_list(Flags, MMod, MSt1),
2763+
% Get the float size (defaults to 64 if nil)
2764+
{MSt3, SizeValue} =
2765+
case Size of
2766+
?TERM_NIL ->
2767+
{MSt2, 64};
2768+
_ ->
2769+
term_to_int(Size, Fail, MMod, MSt2)
2770+
end,
2771+
% Call single primitive with size parameter
2772+
{MSt4, BoolResult} = MMod:call_primitive(MSt3, ?PRIM_BITSTRING_INSERT_FLOAT, [
2773+
CreatedBin, Offset, {free, SrcReg}, SizeValue, {free, FlagsValue}
2774+
]),
2775+
MSt5 = cond_raise_badarg_or_jump_to_fail_label(
2776+
{'(bool)', {free, BoolResult}, '==', false}, Fail, MMod, MSt4
2777+
),
2778+
{MSt6, NewOffset} = first_pass_bs_create_bin_insert_value_increment_offset(
2779+
MMod, MSt5, Offset, SizeValue, 1
2780+
),
2781+
{MSt6, NewOffset, CreatedBin};
27212782
first_pass_bs_create_bin_insert_value(
27222783
string, _Flags, Src, Size, SegmentUnit, Fail, CreatedBin, Offset, MMod, MSt0
27232784
) ->
@@ -3313,6 +3374,13 @@ verify_is_any_integer(Arg1, Fail, MMod, MSt0) ->
33133374
MSt0
33143375
).
33153376

3377+
verify_is_number(Arg1, Fail, MMod, MSt0) ->
3378+
{MSt1, Reg} = MMod:copy_to_native_register(MSt0, Arg1),
3379+
{MSt2, IsNumber} = MMod:call_primitive(MSt1, ?PRIM_TERM_IS_NUMBER, [{free, Reg}]),
3380+
cond_raise_badarg_or_jump_to_fail_label(
3381+
{'(bool)', {free, IsNumber}, '==', false}, Fail, MMod, MSt2
3382+
).
3383+
33163384
%%-----------------------------------------------------------------------------
33173385
%% @doc Test if Arg1 is a binary, jump to FailLabel if it isn't or raise
33183386
%% badarg if FailLabel is 0

libs/jit/src/primitives.hrl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
-define(PRIM_STACKTRACE_BUILD, 71).
9595
-define(PRIM_TERM_REUSE_BINARY, 72).
9696
-define(PRIM_ALLOC_BIG_INTEGER_FRAGMENT, 73).
97+
-define(PRIM_BITSTRING_INSERT_FLOAT, 74).
9798

9899
% Parameters to ?PRIM_MEMORY_ENSURE_FREE_WITH_ROOTS
99100
% -define(MEMORY_NO_SHRINK, 0).

src/libAtomVM/bitstring.c

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,73 @@ void bitstring_copy_bits_incomplete_bytes(uint8_t *dst, size_t bits_offset, cons
330330
*dst = dest_byte;
331331
}
332332

333+
bool bitstring_extract_f16(
334+
term src_bin, size_t offset, avm_int_t n, enum BitstringFlags bs_flags, avm_float_t *dst)
335+
{
336+
unsigned long capacity = term_binary_size(src_bin);
337+
if (8 * capacity - offset < (unsigned long) n) {
338+
return false;
339+
}
340+
341+
if ((offset & 0x7) == 0) {
342+
int byte_offset = offset >> 3;
343+
const uint8_t *src = (const uint8_t *) term_binary_data(src_bin) + byte_offset;
344+
345+
// Read 16-bit value
346+
uint16_t f16_bits;
347+
if (bs_flags & LittleEndianIntegerMask) {
348+
f16_bits = READ_16LE_UNALIGNED(src);
349+
} else {
350+
f16_bits = READ_16_UNALIGNED(src);
351+
}
352+
353+
// Convert IEEE 754 half-precision to single-precision
354+
uint32_t sign = (f16_bits >> 15) & 0x1;
355+
uint32_t f16_exp = (f16_bits >> 10) & 0x1F;
356+
uint32_t f16_mantissa = f16_bits & 0x3FF;
357+
358+
uint32_t f32_bits;
359+
if (f16_exp == 0) {
360+
if (f16_mantissa == 0) {
361+
// Zero
362+
f32_bits = sign << 31;
363+
} else {
364+
// Subnormal number - normalize it
365+
int e = -1;
366+
uint32_t m = f16_mantissa;
367+
do {
368+
e++;
369+
m <<= 1;
370+
} while ((m & 0x400) == 0);
371+
f16_mantissa = m & 0x3FF;
372+
f16_exp = -e;
373+
int32_t f32_exp = (int32_t) f16_exp + 127 - 15;
374+
f32_bits = (sign << 31) | (f32_exp << 23) | (f16_mantissa << 13);
375+
}
376+
} else if (f16_exp == 0x1F) {
377+
// Inf or NaN - not finite
378+
return false;
379+
} else {
380+
// Normalized number
381+
int32_t f32_exp = (int32_t) f16_exp + 127 - 15;
382+
f32_bits = (sign << 31) | (f32_exp << 23) | (f16_mantissa << 13);
383+
}
384+
385+
union
386+
{
387+
uint32_t bits;
388+
float fvalue;
389+
} f32;
390+
f32.bits = f32_bits;
391+
392+
*dst = f32.fvalue;
393+
return true;
394+
} else {
395+
// TODO: add support to floats not aligned to byte boundary
396+
return false;
397+
}
398+
}
399+
333400
bool bitstring_extract_f32(
334401
term src_bin, size_t offset, avm_int_t n, enum BitstringFlags bs_flags, avm_float_t *dst)
335402
{
@@ -423,3 +490,158 @@ intn_from_integer_options_t bitstring_flags_to_intn_opts(enum BitstringFlags bf)
423490
#endif
424491
return converted;
425492
}
493+
494+
bool bitstring_insert_f16(
495+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags)
496+
{
497+
unsigned long capacity = term_binary_size(dst_bin);
498+
if (8 * capacity - offset < 16) {
499+
return false;
500+
}
501+
502+
if (!isfinite(value)) {
503+
return false;
504+
}
505+
506+
if ((offset & 0x7) == 0) {
507+
int byte_offset = offset >> 3;
508+
uint8_t *dst = (uint8_t *) term_binary_data(dst_bin) + byte_offset;
509+
510+
_Static_assert(sizeof(float) == 4, "Unsupported float size");
511+
512+
// Convert double to float first
513+
union
514+
{
515+
uint32_t bits;
516+
float fvalue;
517+
} f32;
518+
519+
f32.fvalue = (float) value;
520+
uint32_t f32_bits = f32.bits;
521+
522+
// Extract components from float (32-bit)
523+
uint32_t sign = (f32_bits >> 31) & 0x1;
524+
int32_t exp = ((f32_bits >> 23) & 0xFF) - 127; // Remove float bias
525+
uint32_t mantissa = f32_bits & 0x7FFFFF;
526+
527+
uint16_t f16_bits;
528+
529+
// Handle special cases
530+
if (exp > 15) {
531+
// Overflow to infinity
532+
f16_bits = (sign << 15) | 0x7C00;
533+
} else if (exp < -14) {
534+
// Underflow to zero or denormal
535+
if (exp < -24) {
536+
// Too small, round to zero
537+
f16_bits = sign << 15;
538+
} else {
539+
// Denormal number
540+
uint32_t denorm_mantissa = (mantissa | 0x800000) >> (-14 - exp);
541+
f16_bits = (sign << 15) | (denorm_mantissa >> 13);
542+
}
543+
} else {
544+
// Normal number
545+
uint32_t f16_exp = exp + 15; // Add half-precision bias
546+
// Round to nearest even (bit 12 is the rounding bit)
547+
uint32_t f16_mantissa = (mantissa + 0x1000) >> 13; // Round and keep top 10 bits
548+
// Handle mantissa overflow
549+
if (f16_mantissa > 0x3FF) {
550+
f16_mantissa = 0;
551+
f16_exp++;
552+
}
553+
if (f16_exp > 30) {
554+
// Overflow to infinity
555+
f16_bits = (sign << 15) | 0x7C00;
556+
} else {
557+
f16_bits = (sign << 15) | (f16_exp << 10) | f16_mantissa;
558+
}
559+
}
560+
561+
if (bs_flags & LittleEndianIntegerMask) {
562+
WRITE_16LE_UNALIGNED(dst, f16_bits);
563+
} else {
564+
WRITE_16_UNALIGNED(dst, f16_bits);
565+
}
566+
return true;
567+
} else {
568+
// TODO: add support to floats not aligned to byte boundary
569+
return false;
570+
}
571+
}
572+
573+
bool bitstring_insert_f32(
574+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags)
575+
{
576+
unsigned long capacity = term_binary_size(dst_bin);
577+
if (8 * capacity - offset < 32) {
578+
return false;
579+
}
580+
581+
if (!isfinite(value)) {
582+
return false;
583+
}
584+
585+
if ((offset & 0x7) == 0) {
586+
int byte_offset = offset >> 3;
587+
uint8_t *dst = (uint8_t *) term_binary_data(dst_bin) + byte_offset;
588+
589+
_Static_assert(sizeof(float) == 4, "Unsupported float size");
590+
591+
union
592+
{
593+
uint32_t bits;
594+
float fvalue;
595+
} f32;
596+
597+
f32.fvalue = (float) value;
598+
599+
if (bs_flags & LittleEndianIntegerMask) {
600+
WRITE_32LE_UNALIGNED(dst, f32.bits);
601+
} else {
602+
WRITE_32_UNALIGNED(dst, f32.bits);
603+
}
604+
return true;
605+
} else {
606+
// TODO: add support to floats not aligned to byte boundary
607+
return false;
608+
}
609+
}
610+
611+
bool bitstring_insert_f64(
612+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags)
613+
{
614+
unsigned long capacity = term_binary_size(dst_bin);
615+
if (8 * capacity - offset < 64) {
616+
return false;
617+
}
618+
619+
if (!isfinite(value)) {
620+
return false;
621+
}
622+
623+
if ((offset & 0x7) == 0) {
624+
int byte_offset = offset >> 3;
625+
uint8_t *dst = (uint8_t *) term_binary_data(dst_bin) + byte_offset;
626+
627+
_Static_assert(sizeof(double) == 8, "Unsupported double size");
628+
629+
union
630+
{
631+
uint64_t bits;
632+
double fvalue;
633+
} f64;
634+
635+
f64.fvalue = value;
636+
637+
if (bs_flags & LittleEndianIntegerMask) {
638+
WRITE_64LE_UNALIGNED(dst, f64.bits);
639+
} else {
640+
WRITE_64_UNALIGNED(dst, f64.bits);
641+
}
642+
return true;
643+
} else {
644+
// TODO: add support to doubles not aligned to byte boundary
645+
return false;
646+
}
647+
}

src/libAtomVM/bitstring.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,13 +503,22 @@ static inline void bitstring_copy_bits(uint8_t *dst, size_t bits_offset, const u
503503
}
504504
}
505505

506+
bool bitstring_extract_f16(
507+
term src_bin, size_t offset, avm_int_t n, enum BitstringFlags bs_flags, avm_float_t *dst);
506508
bool bitstring_extract_f32(
507509
term src_bin, size_t offset, avm_int_t n, enum BitstringFlags bs_flags, avm_float_t *dst);
508510
bool bitstring_extract_f64(
509511
term src_bin, size_t offset, avm_int_t n, enum BitstringFlags bs_flags, avm_float_t *dst);
510512

511513
intn_from_integer_options_t bitstring_flags_to_intn_opts(enum BitstringFlags bf);
512514

515+
bool bitstring_insert_f16(
516+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags);
517+
bool bitstring_insert_f32(
518+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags);
519+
bool bitstring_insert_f64(
520+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags);
521+
513522
#ifdef __cplusplus
514523
}
515524
#endif

src/libAtomVM/defaultatoms.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ X(STRING_ATOM, "\x6", "string")
114114
X(UTF8_ATOM, "\x4", "utf8")
115115
X(UTF16_ATOM, "\x5", "utf16")
116116
X(UTF32_ATOM, "\x5", "utf32")
117+
X(FLOAT_ATOM, "\x5", "float")
117118

118119
X(COPY_ATOM, "\x4", "copy")
119120
X(REUSE_ATOM, "\x5", "reuse")

src/libAtomVM/jit.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,9 @@ static term jit_bitstring_extract_float(Context *ctx, term *bin_ptr, size_t offs
13191319
avm_float_t value;
13201320
bool status;
13211321
switch (n) {
1322+
case 16:
1323+
status = bitstring_extract_f16(((term) bin_ptr) | TERM_PRIMARY_BOXED, offset, n, bs_flags, &value);
1324+
break;
13221325
case 32:
13231326
status = bitstring_extract_f32(((term) bin_ptr) | TERM_PRIMARY_BOXED, offset, n, bs_flags, &value);
13241327
break;
@@ -1435,6 +1438,18 @@ static bool jit_bitstring_insert_integer(term bin, size_t offset, term value, si
14351438
return bitstring_insert_integer(bin, offset, int_value, n, flags);
14361439
}
14371440

1441+
static bool jit_bitstring_insert_float(term bin, size_t offset, term value, size_t n, enum BitstringFlags flags)
1442+
{
1443+
avm_float_t float_value = term_conv_to_float(value);
1444+
if (n == 16) {
1445+
return bitstring_insert_f16(bin, offset, float_value, flags);
1446+
} else if (n == 32) {
1447+
return bitstring_insert_f32(bin, offset, float_value, flags);
1448+
} else {
1449+
return bitstring_insert_f64(bin, offset, float_value, flags);
1450+
}
1451+
}
1452+
14381453
static void jit_bitstring_copy_module_str(Context *ctx, JITState *jit_state, term bin, size_t offset, int str_id, size_t len)
14391454
{
14401455
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 = {
18171832
term_copy_map,
18181833
jit_stacktrace_build,
18191834
jit_term_reuse_binary,
1820-
jit_alloc_big_integer_fragment
1835+
jit_alloc_big_integer_fragment,
1836+
jit_bitstring_insert_float
18211837
};
18221838

18231839
#endif

0 commit comments

Comments
 (0)