Skip to content

Commit 1db55ab

Browse files
committed
Improve support for floats in bitstrings
Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent 29a62fc commit 1db55ab

File tree

9 files changed

+457
-3
lines changed

9 files changed

+457
-3
lines changed

libs/jit/src/jit.erl

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2594,6 +2594,42 @@ first_pass_bs_create_bin_compute_size(
25942594
) ->
25952595
MSt1 = verify_is_integer(Src, Fail, MMod, MSt0),
25962596
{MSt1, AccLiteralSize0 + 32, AccSizeReg0, State0};
2597+
first_pass_bs_create_bin_compute_size(
2598+
float, Src, Size, _SegmentUnit, Fail, AccLiteralSize0, AccSizeReg0, MMod, MSt0, State0
2599+
) ->
2600+
MSt1 = verify_is_number(Src, Fail, MMod, MSt0),
2601+
% Verify and get the float size (defaults to 64 if nil)
2602+
case Size of
2603+
?TERM_NIL ->
2604+
{MSt1, AccLiteralSize0 + 64, AccSizeReg0, State0};
2605+
_ ->
2606+
{MSt2, SizeValue} = term_to_int(Size, Fail, MMod, MSt1),
2607+
if
2608+
is_integer(SizeValue) ->
2609+
% If size is a literal, compiler would only allow 16/32/64.
2610+
{MSt2, AccLiteralSize0 + SizeValue, AccSizeReg0, State0};
2611+
is_atom(SizeValue) ->
2612+
% Check if size is 16, 32, or 64 using 'and' of '!=' checks
2613+
MSt3 = cond_raise_badarg_or_jump_to_fail_label(
2614+
{'and', [
2615+
{SizeValue, '!=', 16},
2616+
{SizeValue, '!=', 32},
2617+
{SizeValue, '!=', 64}
2618+
]},
2619+
Fail,
2620+
MMod,
2621+
MSt2
2622+
),
2623+
case AccSizeReg0 of
2624+
undefined ->
2625+
{MSt3, AccLiteralSize0, SizeValue, State0};
2626+
_ ->
2627+
MSt4 = MMod:add(MSt3, AccSizeReg0, SizeValue),
2628+
MSt5 = MMod:free_native_registers(MSt4, [SizeValue]),
2629+
{MSt5, AccLiteralSize0, AccSizeReg0, State0}
2630+
end
2631+
end
2632+
end;
25972633
first_pass_bs_create_bin_compute_size(
25982634
integer, Src, Size, SegmentUnit, Fail, AccLiteralSize0, AccSizeReg0, MMod, MSt0, State0
25992635
) ->
@@ -2749,6 +2785,31 @@ first_pass_bs_create_bin_insert_value(
27492785
MMod, MSt6, Offset, SizeValue, 1
27502786
),
27512787
{MSt7, NewOffset, CreatedBin};
2788+
first_pass_bs_create_bin_insert_value(
2789+
float, Flags, Src, Size, _SegmentUnit, Fail, CreatedBin, Offset, MMod, MSt0
2790+
) ->
2791+
% Src is a term (boxed float or integer)
2792+
{MSt1, SrcReg} = MMod:move_to_native_register(MSt0, Src),
2793+
{MSt2, FlagsValue} = decode_flags_list(Flags, MMod, MSt1),
2794+
% Get the float size (defaults to 64 if nil)
2795+
{MSt3, SizeValue} =
2796+
case Size of
2797+
?TERM_NIL ->
2798+
{MSt2, 64};
2799+
_ ->
2800+
term_to_int(Size, Fail, MMod, MSt2)
2801+
end,
2802+
% Call single primitive with size parameter
2803+
{MSt4, BoolResult} = MMod:call_primitive(MSt3, ?PRIM_BITSTRING_INSERT_FLOAT, [
2804+
CreatedBin, Offset, {free, SrcReg}, SizeValue, {free, FlagsValue}
2805+
]),
2806+
MSt5 = cond_raise_badarg_or_jump_to_fail_label(
2807+
{'(bool)', {free, BoolResult}, '==', false}, Fail, MMod, MSt4
2808+
),
2809+
{MSt6, NewOffset} = first_pass_bs_create_bin_insert_value_increment_offset(
2810+
MMod, MSt5, Offset, SizeValue, 1
2811+
),
2812+
{MSt6, NewOffset, CreatedBin};
27522813
first_pass_bs_create_bin_insert_value(
27532814
string, _Flags, Src, Size, SegmentUnit, Fail, CreatedBin, Offset, MMod, MSt0
27542815
) ->
@@ -3326,6 +3387,13 @@ verify_is_any_integer(Arg1, Fail, MMod, MSt0) ->
33263387
Arg1, ?TERM_INTEGER_TAG, ?TERM_BOXED_POSITIVE_INTEGER, Fail, MMod, MSt0
33273388
).
33283389

3390+
verify_is_number(Arg1, Fail, MMod, MSt0) ->
3391+
{MSt1, Reg} = MMod:copy_to_native_register(MSt0, Arg1),
3392+
{MSt2, IsNumber} = MMod:call_primitive(MSt1, ?PRIM_TERM_IS_NUMBER, [{free, Reg}]),
3393+
cond_raise_badarg_or_jump_to_fail_label(
3394+
{'(bool)', {free, IsNumber}, '==', false}, Fail, MMod, MSt2
3395+
).
3396+
33293397
%%-----------------------------------------------------------------------------
33303398
%% @doc Test if Arg1 is a binary, jump to FailLabel if it isn't or raise
33313399
%% badarg if FailLabel is 0

libs/jit/src/primitives.hrl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
-define(PRIM_TERM_COPY_MAP, 70).
9494
-define(PRIM_STACKTRACE_BUILD, 71).
9595
-define(PRIM_TERM_REUSE_BINARY, 72).
96+
-define(PRIM_BITSTRING_INSERT_FLOAT, 73).
9697

9798
% Parameters to ?PRIM_MEMORY_ENSURE_FREE_WITH_ROOTS
9899
% -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
{
@@ -406,3 +473,158 @@ bool bitstring_extract_f64(
406473
return false;
407474
}
408475
}
476+
477+
bool bitstring_insert_f16(
478+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags)
479+
{
480+
unsigned long capacity = term_binary_size(dst_bin);
481+
if (8 * capacity - offset < 16) {
482+
return false;
483+
}
484+
485+
if (!isfinite(value)) {
486+
return false;
487+
}
488+
489+
if ((offset & 0x7) == 0) {
490+
int byte_offset = offset >> 3;
491+
uint8_t *dst = (uint8_t *) term_binary_data(dst_bin) + byte_offset;
492+
493+
_Static_assert(sizeof(float) == 4, "Unsupported float size");
494+
495+
// Convert double to float first
496+
union
497+
{
498+
uint32_t bits;
499+
float fvalue;
500+
} f32;
501+
502+
f32.fvalue = (float) value;
503+
uint32_t f32_bits = f32.bits;
504+
505+
// Extract components from float (32-bit)
506+
uint32_t sign = (f32_bits >> 31) & 0x1;
507+
int32_t exp = ((f32_bits >> 23) & 0xFF) - 127; // Remove float bias
508+
uint32_t mantissa = f32_bits & 0x7FFFFF;
509+
510+
uint16_t f16_bits;
511+
512+
// Handle special cases
513+
if (exp > 15) {
514+
// Overflow to infinity
515+
f16_bits = (sign << 15) | 0x7C00;
516+
} else if (exp < -14) {
517+
// Underflow to zero or denormal
518+
if (exp < -24) {
519+
// Too small, round to zero
520+
f16_bits = sign << 15;
521+
} else {
522+
// Denormal number
523+
uint32_t denorm_mantissa = (mantissa | 0x800000) >> (-14 - exp);
524+
f16_bits = (sign << 15) | (denorm_mantissa >> 13);
525+
}
526+
} else {
527+
// Normal number
528+
uint32_t f16_exp = exp + 15; // Add half-precision bias
529+
// Round to nearest even (bit 12 is the rounding bit)
530+
uint32_t f16_mantissa = (mantissa + 0x1000) >> 13; // Round and keep top 10 bits
531+
// Handle mantissa overflow
532+
if (f16_mantissa > 0x3FF) {
533+
f16_mantissa = 0;
534+
f16_exp++;
535+
}
536+
if (f16_exp > 30) {
537+
// Overflow to infinity
538+
f16_bits = (sign << 15) | 0x7C00;
539+
} else {
540+
f16_bits = (sign << 15) | (f16_exp << 10) | f16_mantissa;
541+
}
542+
}
543+
544+
if (bs_flags & LittleEndianIntegerMask) {
545+
WRITE_16LE_UNALIGNED(dst, f16_bits);
546+
} else {
547+
WRITE_16_UNALIGNED(dst, f16_bits);
548+
}
549+
return true;
550+
} else {
551+
// TODO: add support to floats not aligned to byte boundary
552+
return false;
553+
}
554+
}
555+
556+
bool bitstring_insert_f32(
557+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags)
558+
{
559+
unsigned long capacity = term_binary_size(dst_bin);
560+
if (8 * capacity - offset < 32) {
561+
return false;
562+
}
563+
564+
if (!isfinite(value)) {
565+
return false;
566+
}
567+
568+
if ((offset & 0x7) == 0) {
569+
int byte_offset = offset >> 3;
570+
uint8_t *dst = (uint8_t *) term_binary_data(dst_bin) + byte_offset;
571+
572+
_Static_assert(sizeof(float) == 4, "Unsupported float size");
573+
574+
union
575+
{
576+
uint32_t bits;
577+
float fvalue;
578+
} f32;
579+
580+
f32.fvalue = (float) value;
581+
582+
if (bs_flags & LittleEndianIntegerMask) {
583+
WRITE_32LE_UNALIGNED(dst, f32.bits);
584+
} else {
585+
WRITE_32_UNALIGNED(dst, f32.bits);
586+
}
587+
return true;
588+
} else {
589+
// TODO: add support to floats not aligned to byte boundary
590+
return false;
591+
}
592+
}
593+
594+
bool bitstring_insert_f64(
595+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags)
596+
{
597+
unsigned long capacity = term_binary_size(dst_bin);
598+
if (8 * capacity - offset < 64) {
599+
return false;
600+
}
601+
602+
if (!isfinite(value)) {
603+
return false;
604+
}
605+
606+
if ((offset & 0x7) == 0) {
607+
int byte_offset = offset >> 3;
608+
uint8_t *dst = (uint8_t *) term_binary_data(dst_bin) + byte_offset;
609+
610+
_Static_assert(sizeof(double) == 8, "Unsupported double size");
611+
612+
union
613+
{
614+
uint64_t bits;
615+
double fvalue;
616+
} f64;
617+
618+
f64.fvalue = value;
619+
620+
if (bs_flags & LittleEndianIntegerMask) {
621+
WRITE_64LE_UNALIGNED(dst, f64.bits);
622+
} else {
623+
WRITE_64_UNALIGNED(dst, f64.bits);
624+
}
625+
return true;
626+
} else {
627+
// TODO: add support to doubles not aligned to byte boundary
628+
return false;
629+
}
630+
}

src/libAtomVM/bitstring.h

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

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

512+
bool bitstring_insert_f16(
513+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags);
514+
bool bitstring_insert_f32(
515+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags);
516+
bool bitstring_insert_f64(
517+
term dst_bin, size_t offset, avm_float_t value, enum BitstringFlags bs_flags);
518+
510519
#ifdef __cplusplus
511520
}
512521
#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")

0 commit comments

Comments
 (0)