From f5fe9a3a17cbfc9fe9268e556a74f2b08674db17 Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Sat, 20 Jun 2026 11:09:05 +0200 Subject: [PATCH] JIT: add missing memory_ensure_free in jit_bitstring_extract_float Following OP_BS_GET_FLOAT2 in opcodesswitch, reserve space for the boxed float before allocating it. Also fix OP_BS_GET_FLOAT2 to use live and `memory_ensure_free_with_roots`. Signed-off-by: Paul Guyot --- libs/jit/src/jit.erl | 27 +++++------- src/libAtomVM/jit.c | 17 +++++--- src/libAtomVM/jit.h | 2 +- src/libAtomVM/opcodesswitch.h | 4 +- tests/erlang_tests/CMakeLists.txt | 2 + tests/erlang_tests/bs_get_float_gc.erl | 57 ++++++++++++++++++++++++++ tests/test.c | 1 + 7 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 tests/erlang_tests/bs_get_float_gc.erl diff --git a/libs/jit/src/jit.erl b/libs/jit/src/jit.erl index efd301a97e..29328881aa 100644 --- a/libs/jit/src/jit.erl +++ b/libs/jit/src/jit.erl @@ -1334,7 +1334,7 @@ first_pass(<>, MMod, MSt0, State0) -> ?ASSERT_ALL_NATIVE_FREE(MSt0), {Fail, Rest1} = decode_label(Rest0), {MSt1, Src, Rest2} = decode_typed_compact_term(Rest1, MMod, MSt0, State0), - {_Live, Rest3} = decode_literal(Rest2), + {Live, Rest3} = decode_literal(Rest2), {MSt2, Size, Rest4} = decode_typed_compact_term(Rest3, MMod, MSt1, State0), {Unit, Rest5} = decode_literal(Rest4), {FlagsValue, Rest6} = decode_literal(Rest5), @@ -1348,24 +1348,19 @@ first_pass(<>, MMod, MSt0, State0) -> MSt5 = MMod:mul(MSt4, SizeReg, Unit), {MSt5, SizeReg} end, - {MSt7, BSBinaryReg} = MMod:get_array_element(MSt6, MatchStateRegPtr, 1), - {MSt8, BSOffsetReg} = MMod:get_array_element(MSt7, MatchStateRegPtr, 2), - {MSt9, Result} = MMod:call_primitive(MSt8, ?PRIM_BITSTRING_EXTRACT_FLOAT, [ - ctx, {free, BSBinaryReg}, BSOffsetReg, NumBits, {free, FlagsValue} + {MSt7, Result} = MMod:call_primitive(MSt6, ?PRIM_BITSTRING_EXTRACT_FLOAT, [ + ctx, jit_state, {free, MatchStateRegPtr}, {free, NumBits}, {free, FlagsValue}, Live ]), - MSt10 = cond_jump_to_label({Result, '==', ?FALSE_ATOM}, Fail, MMod, MSt9), - MSt11 = MMod:add(MSt10, BSOffsetReg, NumBits), - MSt12 = MMod:free_native_registers(MSt11, [NumBits]), - MSt13 = MMod:move_to_array_element(MSt12, BSOffsetReg, MatchStateRegPtr, 2), - MSt14 = MMod:free_native_registers(MSt13, [BSOffsetReg, MatchStateRegPtr]), - {MSt15, Dest, Rest7} = decode_dest(Rest6, MMod, MSt14), + MSt8 = handle_error_if({Result, '==', 0}, MMod, MSt7), + MSt9 = cond_jump_to_label({Result, '==', ?FALSE_ATOM}, Fail, MMod, MSt8), + {MSt10, Dest, Rest7} = decode_dest(Rest6, MMod, MSt9), ?TRACE("OP_BS_GET_FLOAT2 ~p,~p,~p,~p,~p,~p,~p\n", [ - Fail, Src, _Live, Size, Unit, FlagsValue, Dest + Fail, Src, Live, Size, Unit, FlagsValue, Dest ]), - MSt16 = MMod:move_to_vm_register(MSt15, Result, Dest), - MSt17 = MMod:free_native_registers(MSt16, [Result]), - ?ASSERT_ALL_NATIVE_FREE(MSt17), - first_pass(Rest7, MMod, MSt17, State0); + MSt11 = MMod:move_to_vm_register(MSt10, Result, Dest), + MSt12 = MMod:free_native_registers(MSt11, [Result]), + ?ASSERT_ALL_NATIVE_FREE(MSt12), + first_pass(Rest7, MMod, MSt12, State0); % 119 first_pass(<>, MMod, MSt0, State0) -> ?ASSERT_ALL_NATIVE_FREE(MSt0), diff --git a/src/libAtomVM/jit.c b/src/libAtomVM/jit.c index 8d8bd5b4f8..dc7bd82b04 100644 --- a/src/libAtomVM/jit.c +++ b/src/libAtomVM/jit.c @@ -1514,20 +1514,21 @@ static term jit_bitstring_extract_integer( } } -static term jit_bitstring_extract_float(Context *ctx, term *bin_ptr, size_t offset, int n, int bs_flags) +static term jit_bitstring_extract_float(Context *ctx, JITState *jit_state, term *match_state_ptr, int n, int bs_flags, int live) { - TRACE("jit_bitstring_extract_float: bin_ptr=%p offset=%d n=%d bs_flags=%d\n", (void *) bin_ptr, (int) offset, n, bs_flags); + TRACE("jit_bitstring_extract_float: match_state_ptr=%p n=%d bs_flags=%d live=%d\n", (void *) match_state_ptr, n, bs_flags, live); + avm_int_t offset = (avm_int_t) match_state_ptr[2]; avm_float_t value; bool status; switch (n) { case 16: - status = bitstring_extract_f16((term) (((uintptr_t) bin_ptr) | TERM_PRIMARY_BOXED), offset, n, bs_flags, &value); + status = bitstring_extract_f16(match_state_ptr[1], offset, n, bs_flags, &value); break; case 32: - status = bitstring_extract_f32((term) (((uintptr_t) bin_ptr) | TERM_PRIMARY_BOXED), offset, n, bs_flags, &value); + status = bitstring_extract_f32(match_state_ptr[1], offset, n, bs_flags, &value); break; case 64: - status = bitstring_extract_f64((term) (((uintptr_t) bin_ptr) | TERM_PRIMARY_BOXED), offset, n, bs_flags, &value); + status = bitstring_extract_f64(match_state_ptr[1], offset, n, bs_flags, &value); break; default: status = false; @@ -1535,6 +1536,12 @@ static term jit_bitstring_extract_float(Context *ctx, term *bin_ptr, size_t offs if (UNLIKELY(!status)) { return FALSE_ATOM; } + match_state_ptr[2] = (term) (offset + n); + TRIM_LIVE_REGS(live); + if (UNLIKELY(memory_ensure_free_with_roots(ctx, FLOAT_SIZE, live, ctx->x, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { + set_error(ctx, jit_state, 0, OUT_OF_MEMORY_ATOM); + return term_invalid_term(); + } return term_from_float(value, &ctx->heap); } diff --git a/src/libAtomVM/jit.h b/src/libAtomVM/jit.h index 5a95fa75da..7a5f217745 100644 --- a/src/libAtomVM/jit.h +++ b/src/libAtomVM/jit.h @@ -234,7 +234,7 @@ struct ModuleNativeInterface void *(*malloc)(Context *ctx, JITState *jit_state, size_t sz); void (*free)(void *ptr); term (*put_map_assoc)(Context *ctx, JITState *jit_state, term src, size_t new_entries, size_t num_elements, term *kv); - term (*bitstring_extract_float)(Context *ctx, term *bin_ptr, size_t offset, int n, int bs_flags); + term (*bitstring_extract_float)(Context *ctx, JITState *jit_state, term *match_state_ptr, int n, int bs_flags, int live); int (*module_get_fun_arity)(Module *fun_module, uint32_t fun_index); bool (*bitstring_match_module_str)(Context *ctx, JITState *jit_state, term bin, size_t offset, int str_id, size_t len); term (*bitstring_get_utf8)(term src); diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index 889ff319a8..6f21ba7f74 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -4391,7 +4391,6 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) term src; DECODE_COMPACT_TERM(src, pc); uint32_t live; - UNUSED(live); DECODE_LITERAL(live, pc); term size; DECODE_COMPACT_TERM(size, pc); @@ -4434,7 +4433,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) } else { term_set_match_state_offset(src, bs_offset + increment); - if (UNLIKELY(memory_ensure_free_opt(ctx, FLOAT_SIZE, MEMORY_NO_GC) != MEMORY_GC_OK)) { + TRIM_LIVE_REGS(live); + if (UNLIKELY(memory_ensure_free_with_roots(ctx, FLOAT_SIZE, live, x_regs, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) { RAISE_ERROR(OUT_OF_MEMORY_ATOM); } term t = term_from_float(value, &ctx->heap); diff --git a/tests/erlang_tests/CMakeLists.txt b/tests/erlang_tests/CMakeLists.txt index a4b0f82b08..42a35495c0 100644 --- a/tests/erlang_tests/CMakeLists.txt +++ b/tests/erlang_tests/CMakeLists.txt @@ -568,6 +568,7 @@ compile_erlang(bs_context_byte_size) compile_erlang(bs_get_binary_fixed_size) compile_erlang(bs_get_integer_fixed_size) compile_erlang(bs_get_float_dynamic_size) +compile_erlang(bs_get_float_gc) compile_erlang(test_is_not_equal) compile_assembler(test_is_not_equal_asm) compile_erlang(test_make_fun2) @@ -1135,6 +1136,7 @@ set(erlang_test_beams bs_get_binary_fixed_size.beam bs_get_integer_fixed_size.beam bs_get_float_dynamic_size.beam + bs_get_float_gc.beam test_is_not_equal.beam test_is_not_equal_asm.beam test_make_fun2.beam diff --git a/tests/erlang_tests/bs_get_float_gc.erl b/tests/erlang_tests/bs_get_float_gc.erl new file mode 100644 index 0000000000..3df9c68f29 --- /dev/null +++ b/tests/erlang_tests/bs_get_float_gc.erl @@ -0,0 +1,57 @@ +% +% This file is part of AtomVM. +% +% Copyright 2026 Paul Guyot +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +-module(bs_get_float_gc). + +%% Force the compiler to use bs_get_float2 opcode instead of the +%% newer bs_match opcode. The no_bs_match option was removed in OTP 29. +-if(?OTP_RELEASE =< 28). +-compile([no_bs_match]). +-endif. + +-export([start/0, id/1]). + +start() -> + Bin = id(<<3.14:64/float, 2.5:32/float>>), + Acc = loop(2000, Bin, []), + 2000 = length(Acc), + ok = check(Acc), + 0. + +loop(0, _Bin, Acc) -> + Acc; +loop(N, Bin, Acc) -> + <> = Bin, + _ = id(make_list(32, [])), + loop(N - 1, Bin, [{F64, F32} | Acc]). + +make_list(0, Acc) -> + Acc; +make_list(N, Acc) -> + make_list(N - 1, [N | Acc]). + +check([]) -> + ok; +check([{F64, F32} | Tail]) -> + true = (F64 > 3.139) andalso (F64 < 3.141), + true = (F32 > 2.49) andalso (F32 < 2.51), + check(Tail). + +id(X) -> X. diff --git a/tests/test.c b/tests/test.c index 342c142fd4..866f2c9bab 100644 --- a/tests/test.c +++ b/tests/test.c @@ -534,6 +534,7 @@ struct Test tests[] = { TEST_CASE(bs_get_binary_fixed_size), TEST_CASE(bs_get_integer_fixed_size), TEST_CASE(bs_get_float_dynamic_size), + TEST_CASE(bs_get_float_gc), TEST_CASE(test_is_not_equal), TEST_CASE(test_make_fun2), TEST_CASE(test_allocate_zero),