diff --git a/libs/jit/src/jit.erl b/libs/jit/src/jit.erl index 0e370cbf5..76710a511 100644 --- a/libs/jit/src/jit.erl +++ b/libs/jit/src/jit.erl @@ -1333,10 +1333,6 @@ first_pass(<>, MMod, MSt0, State0) -> {MSt5, BSOffsetReg0} = MMod:get_array_element(MSt4, MatchStateRegPtr, 2), MSt6 = if - Unit =/= 8 -> - MMod:call_primitive_last(MSt5, ?PRIM_RAISE_ERROR, [ - ctx, jit_state, offset, ?UNSUPPORTED_ATOM - ]); FlagsValue =/= 0 -> MMod:call_primitive_last(MSt5, ?PRIM_RAISE_ERROR, [ ctx, jit_state, offset, ?UNSUPPORTED_ATOM @@ -1356,13 +1352,21 @@ first_pass(<>, MMod, MSt0, State0) -> MSt11 = MMod:sub(MSt10, SizeReg, BSOffsetReg1), {MSt11, SizeReg}; is_integer(Size) -> - % SizeReg is binary size + % SizeReg contains binary size in raw bytes (not a term) % Size is a tagged integer: (N bsl 4) bor 0xF - % SizeBytes is the raw byte count - SizeBytes = Size bsr 4, - MSt11 = MMod:sub(MSt10, SizeReg, SizeBytes), + % SizeInUnits is the raw count in units + SizeInUnits = Size bsr 4, + MSt11 = + if + (SizeInUnits * Unit) rem 8 =/= 0 -> + MMod:call_primitive_last(MSt10, ?PRIM_RAISE_ERROR, [ + ctx, jit_state, offset, ?UNSUPPORTED_ATOM + ]); + true -> + MMod:sub(MSt10, SizeReg, (SizeInUnits * Unit) div 8) + end, MSt12 = cond_jump_to_label({{free, SizeReg}, '<', BSOffsetReg1}, Fail, MMod, MSt11), - {MSt12, SizeBytes}; + {MSt12, (SizeInUnits * Unit) div 8}; true -> {MSt11, SizeValReg} = MMod:move_to_native_register(MSt10, Size), MSt12 = MMod:if_else_block( @@ -1374,10 +1378,32 @@ first_pass(<>, MMod, MSt0, State0) -> end, fun(BSt0) -> {BSt1, SizeValReg} = term_to_int(SizeValReg, 0, MMod, BSt0), - BSt2 = MMod:sub(BSt1, SizeReg, SizeValReg), - BSt3 = cond_jump_to_label({SizeReg, '<', BSOffsetReg1}, Fail, MMod, BSt2), - BSt4 = MMod:move_to_native_register(BSt3, SizeValReg, SizeReg), - MMod:free_native_registers(BSt4, [SizeValReg]) + {BSt2, SizeValReg2} = + if + is_integer(SizeValReg) -> + if + (SizeValReg * Unit) rem 8 =/= 0 -> + MMod:call_primitive_last(BSt1, ?PRIM_RAISE_ERROR, [ + ctx, jit_state, offset, ?UNSUPPORTED_ATOM + ]); + true -> + {BSt1, (SizeValReg * Unit) div 8} + end; + true -> + BBSt1 = MMod:mul(BSt1, SizeValReg, Unit), + BBSt2 = MMod:if_block( + BBSt1, {SizeValReg, '&', 16#7, '!=', 0}, fun(BlockSt) -> + MMod:call_primitive_last(BlockSt, ?PRIM_RAISE_ERROR, [ + ctx, jit_state, offset, ?UNSUPPORTED_ATOM + ]) + end + ), + MMod:shift_right(BBSt2, SizeValReg, 3) + end, + BSt3 = MMod:sub(BSt2, SizeReg, SizeValReg2), + BSt4 = cond_jump_to_label({SizeReg, '<', BSOffsetReg1}, Fail, MMod, BSt3), + BSt5 = MMod:move_to_native_register(BSt4, SizeValReg2, SizeReg), + MMod:free_native_registers(BSt5, [SizeValReg]) end ), {MSt12, SizeReg} diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index 2473e00b5..c7ed09881 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -5570,20 +5570,21 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) term bs_bin = term_get_match_state_binary(src); avm_int_t bs_offset = term_get_match_state_offset(src); - if (unit != 8) { - TRACE("bs_get_binary2: Unsupported: unit must be 8.\n"); - RAISE_ERROR(UNSUPPORTED_ATOM); - } avm_int_t size_val = 0; if (term_is_integer(size)) { - size_val = term_to_int(size); + size_val = term_to_int(size) * unit; + if (size_val % 8) { + TRACE("bs_get_binary2: Unsupported: size must be divisible by 8, got: %ld\n", size_val); + RAISE_ERROR(UNSUPPORTED_ATOM); + } + size_val = size_val / 8; } else if (size == ALL_ATOM) { size_val = term_binary_size(bs_bin) - bs_offset / 8; } else { TRACE("bs_get_binary2: size is neither an integer nor the atom `all`\n"); RAISE_ERROR(BADARG_ATOM); } - if (bs_offset % unit != 0) { + if (bs_offset % 8 != 0) { TRACE("bs_get_binary2: Unsupported. Offset on binary read must be aligned on byte boundaries.\n"); RAISE_ERROR(BADARG_ATOM); } @@ -5594,11 +5595,11 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) TRACE("bs_get_binary2/7, fail=%u src=%p live=%u unit=%u\n", (unsigned) fail, (void *) bs_bin, (unsigned) live, (unsigned) unit); - if ((unsigned int) (bs_offset / unit + size_val) > term_binary_size(bs_bin)) { + if ((unsigned int) (bs_offset / 8 + size_val) > term_binary_size(bs_bin)) { TRACE("bs_get_binary2: insufficient capacity -- bs_offset = %d, size_val = %d\n", (int) bs_offset, (int) size_val); JUMP_TO_ADDRESS(mod->labels[fail]); } else { - term_set_match_state_offset(src, bs_offset + size_val * unit); + term_set_match_state_offset(src, bs_offset + size_val * 8); TRIM_LIVE_REGS(live); // there is always room for a MAX_REG + 1 register, used as working register @@ -5615,7 +5616,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP bs_bin = x_regs[live]; - term t = term_maybe_create_sub_binary(bs_bin, bs_offset / unit, size_val, &ctx->heap, ctx->global); + term t = term_maybe_create_sub_binary(bs_bin, bs_offset / 8, size_val, &ctx->heap, ctx->global); WRITE_REGISTER(dreg, t); } #endif @@ -7109,7 +7110,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) JUMP_TO_LABEL(mod, fail); } } - segment_size = signed_size_value; + segment_size = size_in_bytes; + segment_unit = 8; } break; } diff --git a/tests/erlang_tests/test_bs.erl b/tests/erlang_tests/test_bs.erl index 55b9abcbb..2d6c7cc84 100644 --- a/tests/erlang_tests/test_bs.erl +++ b/tests/erlang_tests/test_bs.erl @@ -105,6 +105,7 @@ start() -> ok = test_bs_variable_size_bitstring(), ok = test_float(), + ok = test_bs_match_bitstring_modifier(), 0. @@ -701,6 +702,60 @@ test_integer_outside_float_limits() -> create_float_binary(Value, Size) -> <>. +test_bs_match_bitstring_modifier() -> + ok = + try + bitstring_match(id(<<123, 234, 245>>), id(15)), + case erlang:system_info(machine) of + "BEAM" -> ok; + "ATOM" -> unexpected + end + catch + error:unsupported -> ok + end, + + {<<123, 234>>, <<245>>} = bitstring_match(id(<<123, 234, 245>>), id(16)), + + %% Non-zero offset + dynamic size + {<<234, 245>>, <<>>} = bitstring_match_offset(id(<<123, 234, 245>>), id(16)), + + %% Size=0 — matched should be empty binary + {<<>>, <<1, 2, 3>>} = bitstring_match(id(<<1, 2, 3>>), id(0)), + + %% Negative dynamic size — should raise an error + ok = + try + bitstring_match(id(<<1, 2, 3>>), id(-1)), + unexpected + catch + error:{badmatch, _} -> ok; + error:badarg -> ok; + error:unsupported -> ok + end, + + %% Constant size with unit=1, byte-aligned (compile-time) + {<<1, 2>>, <<3>>} = bitstring_match_const_16(id(<<1, 2, 3>>)), + + %% Non-zero offset + constant size + {<<2, 3>>, <<4>>} = bitstring_match_offset_const(id(<<1, 2, 3, 4>>)), + + ok. + +bitstring_match(BS, Size) -> + <> = BS, + {Matched, Rest}. + +bitstring_match_offset(BS, Size) -> + <<_Skip:8, Matched:Size/bitstring, Rest/bits>> = BS, + {Matched, Rest}. + +bitstring_match_const_16(BS) -> + <> = BS, + {Matched, Rest}. + +bitstring_match_offset_const(BS) -> + <<_Skip:8, Matched:16/bitstring, Rest/bits>> = BS, + {Matched, Rest}. check_x86_64_jt(<<>>) -> ok; check_x86_64_jt(<<16#e9, _Offset:32/little, Tail/binary>>) -> check_x86_64_jt(Tail);