diff --git a/CMakeLists.txt b/CMakeLists.txt index c4971a341..67e35d125 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ project(AtomVM) set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeModules) find_package(Dialyzer) +find_package(Erlang) find_package(Elixir) option(AVM_DISABLE_FP "Disable floating point support." OFF) diff --git a/CMakeModules/FindErlang.cmake b/CMakeModules/FindErlang.cmake new file mode 100644 index 000000000..51746645b --- /dev/null +++ b/CMakeModules/FindErlang.cmake @@ -0,0 +1,32 @@ +# +# This file is part of AtomVM. +# +# Copyright 2025 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 +# + +find_program(ERLC_PATH erlc) +find_program(ERL_PATH erl) + +if (ERLC_PATH AND ERL_PATH) + set(Erlang_FOUND TRUE) + + execute_process(COMMAND ${ERL_PATH} -eval "io:put_chars(erlang:system_info(otp_release))" -s init stop -noshell + OUTPUT_VARIABLE Erlang_VERSION) + message("Found Erlang OTP ${Erlang_VERSION}") +elseif(Erlang_FIND_REQUIRED) + message(FATAL_ERROR "Erlang or Erlang compiler not found") +endif() diff --git a/src/libAtomVM/opcodesswitch.h b/src/libAtomVM/opcodesswitch.h index cf2ffbaee..5fc8a9a35 100644 --- a/src/libAtomVM/opcodesswitch.h +++ b/src/libAtomVM/opcodesswitch.h @@ -973,11 +973,13 @@ static void destroy_extended_registers(Context *ctx, unsigned int live) #ifndef TRACE_JUMP #define JUMP_TO_ADDRESS(address) \ - pc = (address) + pc = (address); \ + goto loop #else #define JUMP_TO_ADDRESS(address) \ pc = (address); \ - fprintf(stderr, "going to jump to %" PRIuPTR "\n", (uintptr_t) (pc - code)) + fprintf(stderr, "going to jump to %" PRIuPTR "\n", (uintptr_t) (pc - code)); \ + goto loop #endif #define SCHEDULE_NEXT(restore_mod, restore_to) \ @@ -1160,22 +1162,34 @@ static void destroy_extended_registers(Context *ctx, unsigned int live) x_regs[2] = stacktrace_create_raw(ctx, mod, pc - code, x_regs[0]); \ goto handle_error; -#define VERIFY_IS_INTEGER(t, opcode_name) \ +#define VERIFY_IS_INTEGER(t, opcode_name, label) \ if (UNLIKELY(!term_is_integer(t))) { \ TRACE(opcode_name ": " #t " is not an integer\n"); \ - RAISE_ERROR(BADARG_ATOM); \ + if (label == 0) { \ + RAISE_ERROR(BADARG_ATOM); \ + } else { \ + JUMP_TO_LABEL(mod, label); \ + } \ } -#define VERIFY_IS_ANY_INTEGER(t, opcode_name) \ +#define VERIFY_IS_ANY_INTEGER(t, opcode_name, label) \ if (UNLIKELY(!term_is_any_integer(t))) { \ TRACE(opcode_name ": " #t " is not any integer\n"); \ - RAISE_ERROR(BADARG_ATOM); \ + if (label == 0) { \ + RAISE_ERROR(BADARG_ATOM); \ + } else { \ + JUMP_TO_LABEL(mod, label); \ + } \ } -#define VERIFY_IS_BINARY(t, opcode_name) \ +#define VERIFY_IS_BINARY(t, opcode_name, label) \ if (UNLIKELY(!term_is_binary(t))) { \ TRACE(opcode_name ": " #t " is not a binary\n"); \ - RAISE_ERROR(BADARG_ATOM); \ + if (label == 0) { \ + RAISE_ERROR(BADARG_ATOM); \ + } else { \ + JUMP_TO_LABEL(mod, label); \ + } \ } #define VERIFY_IS_MATCH_STATE(t, opcode_name) \ @@ -1893,7 +1907,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) prev_mod = mod; code = mod->code->code; x_regs = ctx->x; - JUMP_TO_ADDRESS(ctx->saved_ip); + pc = (ctx->saved_ip); remaining_reductions = DEFAULT_REDUCTIONS_AMOUNT; #pragma GCC diagnostic push @@ -1915,7 +1929,9 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) while (1) { TRACE("-- loop -- i = %" PRIuPTR ", next ocopde = %d\n", pc - code, *pc); - +#ifdef IMPL_EXECUTE_LOOP +loop: +#endif switch (*pc++) { case OP_LABEL: { uint32_t label; @@ -3861,8 +3877,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_INTEGER(src1, "bs_add"); - VERIFY_IS_INTEGER(src2, "bs_add"); + VERIFY_IS_INTEGER(src1, "bs_add", 0); + VERIFY_IS_INTEGER(src2, "bs_add", 0); avm_int_t src1_val = term_to_int(src1); avm_int_t src2_val = term_to_int(src2); @@ -3892,7 +3908,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_INTEGER(size, "bs_init2"); + VERIFY_IS_INTEGER(size, "bs_init2", 0); avm_int_t size_val = term_to_int(size); TRIM_LIVE_REGS(live); @@ -3932,7 +3948,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_INTEGER(size, "bs_init_bits"); + VERIFY_IS_INTEGER(size, "bs_init_bits", 0); avm_int_t size_val = term_to_int(size); if (size_val % 8 != 0) { TRACE("bs_init_bits: size_val (%li) is not evenly divisible by 8\n", size_val); @@ -3974,7 +3990,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) TRACE("bs_utf8_size/3"); #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_INTEGER(src, "bs_utf8_size/3"); + VERIFY_IS_INTEGER(src, "bs_utf8_size/3", 0); avm_int_t src_value = term_to_int(src); TRACE("bs_utf8_size/3 fail=%i src=0x%lx dreg=%c%i\n", fail, (long) src_value, T_DEST_REG(dreg)); size_t utf8_size; @@ -4001,7 +4017,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) } #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_INTEGER(src, "bs_put_utf8/3"); + VERIFY_IS_INTEGER(src, "bs_put_utf8/3", 0); avm_int_t src_value = term_to_int(src); TRACE("bs_put_utf8/3 flags=%x, src=0x%lx\n", (int) flags, (long) src_value); if (UNLIKELY(!term_is_binary(ctx->bs))) { @@ -4111,7 +4127,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) TRACE("bs_utf16_size/3"); #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_INTEGER(src, "bs_utf16_size/3"); + VERIFY_IS_INTEGER(src, "bs_utf16_size/3", 0); avm_int_t src_value = term_to_int(src); TRACE("bs_utf16_size/3 fail=%i src=0x%lx dreg=%c%i\n", fail, (long) src_value, T_DEST_REG(dreg)); size_t utf16_size; @@ -4138,7 +4154,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) } #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_INTEGER(src, "bs_put_utf16/3"); + VERIFY_IS_INTEGER(src, "bs_put_utf16/3", 0); avm_int_t src_value = term_to_int(src); TRACE("bs_put_utf16/3 flags=%x, src=0x%lx\n", (int) flags, src_value); if (UNLIKELY(!term_is_binary(ctx->bs))) { @@ -4252,7 +4268,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) } #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_INTEGER(src, "bs_put_utf32/3"); + VERIFY_IS_INTEGER(src, "bs_put_utf32/3", 0); avm_int_t src_value = term_to_int(src); TRACE("bs_put_utf32/3 flags=%x, src=0x%lx\n", (int) flags, (long) src_value); if (UNLIKELY(!term_is_binary(ctx->bs))) { @@ -4389,9 +4405,9 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_BINARY(src, "bs_append"); - VERIFY_IS_INTEGER(size, "bs_append"); - VERIFY_IS_INTEGER(extra, "bs_append"); + VERIFY_IS_BINARY(src, "bs_append", 0); + VERIFY_IS_INTEGER(size, "bs_append", 0); + VERIFY_IS_INTEGER(extra, "bs_append", 0); avm_int_t size_val = term_to_int(size); avm_int_t extra_val = term_to_int(extra); @@ -4452,8 +4468,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_BINARY(src, "bs_private_append"); - VERIFY_IS_INTEGER(size, "bs_private_append"); + VERIFY_IS_BINARY(src, "bs_private_append", 0); + VERIFY_IS_INTEGER(size, "bs_private_append", 0); avm_int_t size_val = term_to_int(size); if (size_val % 8 != 0) { @@ -4501,8 +4517,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_ANY_INTEGER(src, "bs_put_integer"); - VERIFY_IS_INTEGER(size, "bs_put_integer"); + VERIFY_IS_ANY_INTEGER(src, "bs_put_integer", 0); + VERIFY_IS_INTEGER(size, "bs_put_integer", 0); avm_int64_t src_value = term_maybe_unbox_int64(src); avm_int_t size_value = term_to_int(size); @@ -4537,7 +4553,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #endif #ifdef IMPL_EXECUTE_LOOP - VERIFY_IS_BINARY(src, "bs_put_binary"); + VERIFY_IS_BINARY(src, "bs_put_binary", 0); unsigned long size_val = 0; if (term_is_integer(size)) { avm_int_t bit_size = term_to_int(size) * unit; @@ -4782,7 +4798,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP VERIFY_IS_MATCH_STATE(src, "bs_set_position"); - VERIFY_IS_INTEGER(pos, "bs_set_position"); + VERIFY_IS_INTEGER(pos, "bs_set_position", 0); avm_int_t pos_val = term_to_int(pos); TRACE("bs_set_position/2 src=0x%lx pos=%li\n", src, pos_val); @@ -4951,7 +4967,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP VERIFY_IS_MATCH_STATE(src, "bs_skip_bits2"); - VERIFY_IS_INTEGER(size, "bs_skip_bits2"); + VERIFY_IS_INTEGER(size, "bs_skip_bits2", 0); if (flags_value != 0) { TRACE("bs_skip_bits2: neither signed nor native or little endian encoding supported.\n"); RAISE_ERROR(UNSUPPORTED_ATOM); @@ -5050,7 +5066,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP VERIFY_IS_MATCH_STATE(src, "bs_get_integer"); - VERIFY_IS_INTEGER(size, "bs_get_integer"); + VERIFY_IS_INTEGER(size, "bs_get_integer", 0); avm_int_t size_val = term_to_int(size); @@ -5103,7 +5119,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP VERIFY_IS_MATCH_STATE(src, "bs_get_float"); - VERIFY_IS_INTEGER(size, "bs_get_float"); + VERIFY_IS_INTEGER(size, "bs_get_float", 0); avm_int_t size_val = term_to_int(size); @@ -6567,7 +6583,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) AVM_ABORT(); } #endif - // Compute binary size in first iteration + // Verify parameters and compute binary size in first iteration #ifdef IMPL_EXECUTE_LOOP size_t binary_size = 0; #endif @@ -6585,61 +6601,104 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) term size; DECODE_COMPACT_TERM(size, pc); #ifdef IMPL_EXECUTE_LOOP - avm_int_t src_value = 0; + size_t segment_size = 0; switch (atom_type) { - case UTF8_ATOM: - case UTF16_ATOM: - case UTF32_ATOM: - VERIFY_IS_INTEGER(src, "bs_create_bin/6"); - src_value = term_to_int(src); + // OTP ignores size for these types + case UTF8_ATOM: { + VERIFY_IS_INTEGER(src, "bs_create_bin/6", fail); + // Silently ignore segment_unit != 0 + segment_unit = 8; + avm_int_t src_value = term_to_int(src); + if (UNLIKELY(!bitstring_utf8_size(src_value, &segment_size))) { + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); + } + } break; - } - size_t segment_size = 0; - switch (size) { - case UNDEFINED_ATOM: { + } + case UTF16_ATOM: { + VERIFY_IS_INTEGER(src, "bs_create_bin/6", fail); // Silently ignore segment_unit != 0 segment_unit = 8; - switch (atom_type) { - case UTF8_ATOM: { - if (UNLIKELY(!bitstring_utf8_size(src_value, &segment_size))) { - RAISE_ERROR(BADARG_ATOM); - } - break; + avm_int_t src_value = term_to_int(src); + if (UNLIKELY(!bitstring_utf16_size(src_value, &segment_size))) { + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); } - case UTF16_ATOM: { - if (UNLIKELY(!bitstring_utf16_size(src_value, &segment_size))) { - RAISE_ERROR(BADARG_ATOM); - } - break; + } + break; + } + case UTF32_ATOM: { + VERIFY_IS_INTEGER(src, "bs_create_bin/6", fail); + // Silently ignore segment_unit != 0 + segment_unit = 8; + segment_size = 4; + break; + } + case INTEGER_ATOM: { + VERIFY_IS_ANY_INTEGER(src, "bs_create_bin/6", fail); + VERIFY_IS_INTEGER(size, "bs_create_bin/6", fail); + avm_int_t signed_size_value = term_to_int(size); + if (UNLIKELY(signed_size_value < 0)) { + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); } - case UTF32_ATOM: { - segment_size = 4; - break; + } + segment_size = signed_size_value; + break; + } + case STRING_ATOM: { + VERIFY_IS_INTEGER(size, "bs_create_bin/6", fail); + avm_int_t signed_size_value = term_to_int(size); + if (UNLIKELY(signed_size_value < 0)) { + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); } - default: - // In Erlang/OTP #5281, this is a compile time check - fprintf(stderr, "Unexpected type %lx for bs_create_bin/6 size undefined\n", (long) atom_type); - AVM_ABORT(); } + segment_size = signed_size_value; break; } - case ALL_ATOM: { - if (atom_type != BINARY_ATOM && atom_type != APPEND_ATOM && atom_type != PRIVATE_APPEND_ATOM) { - // In Erlang/OTP #5281, this is a compile time check - fprintf(stderr, "Unexpected type for bs_create_bin/6 size all\n"); - AVM_ABORT(); + case APPEND_ATOM: + case BINARY_ATOM: + case PRIVATE_APPEND_ATOM: { + VERIFY_IS_BINARY(src, "bs_create_bin/6", fail); + if (size == ALL_ATOM) { + // We only support src as a binary of bytes here. + segment_size = term_binary_size(src); + segment_unit = 8; + } else { + VERIFY_IS_INTEGER(size, "bs_create_bin/6", fail); + avm_int_t signed_size_value = term_to_int(size); + if (UNLIKELY(signed_size_value < 0)) { + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); + } + } + size_t binary_size = term_binary_size(src); + if ((size_t) signed_size_value > binary_size) { + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); + } + } + segment_size = signed_size_value; } - VERIFY_IS_BINARY(src, "bs_create_bin/6"); - // We only support src as a binary of bytes here. - segment_size = term_binary_size(src); - segment_unit = 8; break; } default: { - if (UNLIKELY(!term_is_integer(size) || term_to_int(size) < 0)) { - RAISE_ERROR(BADARG_ATOM); - } - segment_size = term_to_int(size); + TRACE("bs_create_bin/6: unsupported type atom_index=%i\n", (int) term_to_atom_index(atom_type)); + RAISE_ERROR(UNSUPPORTED_ATOM); } } binary_size += segment_unit * segment_size; @@ -6689,11 +6748,9 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) case UTF8_ATOM: case UTF16_ATOM: case UTF32_ATOM: - VERIFY_IS_INTEGER(src, "bs_create_bin/6"); src_value = term_to_int(src); break; case INTEGER_ATOM: - VERIFY_IS_ANY_INTEGER(src, "bs_create_bin/6"); src_value = term_maybe_unbox_int64(src); break; default: @@ -6702,13 +6759,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) switch (atom_type) { case INTEGER_ATOM: case STRING_ATOM: - VERIFY_IS_INTEGER(size, "bs_create_bin/6"); - avm_int_t signed_size_value = term_to_int(size); - if (UNLIKELY(signed_size_value < 0)) { - TRACE("bs_create_bin/6: size value less than 0: %i\n", (int) signed_size_value); - RAISE_ERROR(BADARG_ATOM); - } - size_value = (size_t) signed_size_value; + size_value = (size_t) term_to_int(size); break; default: break; @@ -6716,27 +6767,29 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) switch (atom_type) { case UTF8_ATOM: { bool result = bitstring_insert_utf8(t, offset, src_value, &segment_size); - if (UNLIKELY(!result)) { - TRACE("bs_create_bin/6: Failed to insert character as utf8 into binary: %i\n", result); - RAISE_ERROR(BADARG_ATOM); - } + // bitstring_utf8_size was called so bitstring_insert_utf8 succeeds + UNUSED(result); + assert(result); segment_size *= 8; break; } case UTF16_ATOM: { bool result = bitstring_insert_utf16(t, offset, src_value, flags_value, &segment_size); - if (UNLIKELY(!result)) { - TRACE("bs_create_bin/6: Failed to insert character as utf16 into binary: %i\n", result); - RAISE_ERROR(BADARG_ATOM); - } + // bitstring_utf16_size was called so bitstring_insert_utf16 succeeds + UNUSED(result); + assert(result); segment_size *= 8; break; } case UTF32_ATOM: { bool result = bitstring_insert_utf32(t, offset, src_value, flags_value); if (UNLIKELY(!result)) { - TRACE("bs_create_bin/6: Failed to insert character as utf16 into binary: %i\n", result); - RAISE_ERROR(BADARG_ATOM); + TRACE("bs_create_bin/6: Failed to insert character as utf32 into binary: %i\n", result); + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); + } } segment_size = 32; break; @@ -6765,12 +6818,12 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) TRACE("bs_create_bin/6: current offset (%li) is not evenly divisible by 8\n", offset); RAISE_ERROR(UNSUPPORTED_ATOM); } - VERIFY_IS_BINARY(src, "bs_create_bin/6"); + VERIFY_IS_BINARY(src, "bs_create_bin/6", fail); uint8_t *dst = (uint8_t *) term_binary_data(t) + (offset / 8); const uint8_t *bin = (const uint8_t *) term_binary_data(src); size_t binary_size = term_binary_size(src); if (size != ALL_ATOM) { - VERIFY_IS_INTEGER(size, "bs_create_bin/6"); + VERIFY_IS_INTEGER(size, "bs_create_bin/6", fail); avm_int_t signed_size_value = term_to_int(size); if (UNLIKELY(signed_size_value < 0)) { TRACE("bs_create_bin/6: size value less than 0: %i\n", (int) signed_size_value); @@ -6778,7 +6831,11 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) } size_value = (size_t) signed_size_value; if (size_value > binary_size) { - RAISE_ERROR(BADARG_ATOM); + if (fail == 0) { + RAISE_ERROR(BADARG_ATOM); + } else { + JUMP_TO_LABEL(mod, fail); + } } binary_size = size_value; } @@ -6786,10 +6843,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) segment_size = binary_size * 8; break; } - default: { - TRACE("bs_create_bin/6: unsupported type atom_index=%i\n", (int) term_to_atom_index(atom_type)); - RAISE_ERROR(UNSUPPORTED_ATOM); - } + default: + UNREACHABLE(); } offset += segment_size; } @@ -6998,7 +7053,7 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) j++; #ifdef IMPL_EXECUTE_LOOP // context_clean_registers(ctx, live); // TODO: check if needed - VERIFY_IS_INTEGER(size, "bs_match/3"); + VERIFY_IS_INTEGER(size, "bs_match/3", fail); avm_int_t size_val = term_to_int(size); avm_int_t increment = size_val * unit; union maybe_unsigned_int64 value; @@ -7156,7 +7211,6 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) #ifdef IMPL_EXECUTE_LOOP bs_match_jump_to_fail: JUMP_TO_ADDRESS(mod->labels[fail]); - continue; #endif } #endif @@ -7183,7 +7237,6 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb) if (target_label) { code = mod->code->code; JUMP_TO_ADDRESS(mod->labels[target_label]); - continue; } } diff --git a/tests/erlang_tests/CMakeLists.txt b/tests/erlang_tests/CMakeLists.txt index 5dd977cb2..66a30221c 100644 --- a/tests/erlang_tests/CMakeLists.txt +++ b/tests/erlang_tests/CMakeLists.txt @@ -30,6 +30,15 @@ function(compile_erlang module_name) ) endfunction() +function(compile_assembler module_name) + add_custom_command( + OUTPUT ${module_name}.beam + COMMAND erlc ${CMAKE_CURRENT_SOURCE_DIR}/${module_name}.S + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${module_name}.S + COMMENT "Compiling ${module_name}.S" + ) +endfunction() + set(TO_HRL_PATH ${CMAKE_CURRENT_LIST_DIR}) function(generate_hrl out_file def_name in_file) @@ -518,6 +527,17 @@ compile_erlang(maps_nifs) compile_erlang(test_raw_raise) +compile_erlang(test_op_bs_create_bin) +compile_assembler(test_op_bs_create_bin_asm) + +if(Erlang_VERSION VERSION_GREATER_EQUAL "25") + set(OTP25_OR_GREATER_TESTS + test_op_bs_create_bin_asm.beam + ) +else() + set(OTP25_OR_GREATER_TESTS) +endif() + add_custom_target(erlang_test_modules DEPENDS code_load_files @@ -1000,4 +1020,8 @@ add_custom_target(erlang_test_modules DEPENDS maps_nifs.beam test_raw_raise.beam + + test_op_bs_create_bin.beam + + ${OTP25_OR_GREATER_TESTS} ) diff --git a/tests/erlang_tests/test_op_bs_create_bin.erl b/tests/erlang_tests/test_op_bs_create_bin.erl new file mode 100644 index 000000000..d8335aae8 --- /dev/null +++ b/tests/erlang_tests/test_op_bs_create_bin.erl @@ -0,0 +1,268 @@ +% +% This file is part of AtomVM. +% +% Copyright 2025 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(test_op_bs_create_bin). + +-export([start/0]). + +start() -> + HasBSCreateBin = + case erlang:system_info(machine) of + "BEAM" -> + erlang:system_info(otp_release) >= "25"; + "ATOM" -> + % If code was compiled with OTP < 25, we won't have bs_create_bin asm file + ?OTP_RELEASE >= 25 + end, + ok = + if + HasBSCreateBin -> + ok = test_bs_create_bin_utf8(), + ok = test_bs_create_bin_utf16(), + ok = test_bs_create_bin_utf32(), + ok = test_bs_create_bin_integer(), + ok = test_bs_create_bin_binary(); + true -> + ok + end, + 0. + +test_bs_create_bin_utf8() -> + <<0>> = test_op_bs_create_bin_asm:bs_create_bin_utf8_no_fail(0, [], undefined), + <<0>> = test_op_bs_create_bin_asm:bs_create_bin_utf8_fail(0, [], undefined), + <<1>> = test_op_bs_create_bin_asm:bs_create_bin_utf8_no_fail(1, [], undefined), + <<1>> = test_op_bs_create_bin_asm:bs_create_bin_utf8_fail(1, [], undefined), + <<227, 130, 162>> = test_op_bs_create_bin_asm:bs_create_bin_utf8_no_fail(12450, [], undefined), + <<227, 130, 162>> = test_op_bs_create_bin_asm:bs_create_bin_utf8_fail(12450, [], undefined), + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_utf8_no_fail(-1, [], undefined), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_utf8_fail(-1, [], undefined), + ok + catch + error:badarg -> unexpected + end, + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_utf8_no_fail(not_int, [], undefined), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_utf8_fail(not_int, [], undefined), + ok + catch + error:badarg -> unexpected + end, + <<0>> = test_op_bs_create_bin_asm:bs_create_bin_utf8_no_fail(0, [], all), + <<0>> = test_op_bs_create_bin_asm:bs_create_bin_utf8_no_fail(0, [], 8), + <<0>> = test_op_bs_create_bin_asm:bs_create_bin_utf8_no_fail(0, [], 16), + <<0>> = test_op_bs_create_bin_asm:bs_create_bin_utf8_no_fail(0, [], foo), + ok. + +test_bs_create_bin_utf16() -> + <<0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf16_no_fail(0, [], undefined), + <<0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf16_fail(0, [], undefined), + <<0, 1>> = test_op_bs_create_bin_asm:bs_create_bin_utf16_no_fail(1, [], undefined), + <<0, 1>> = test_op_bs_create_bin_asm:bs_create_bin_utf16_fail(1, [], undefined), + <<48, 162>> = test_op_bs_create_bin_asm:bs_create_bin_utf16_no_fail(12450, [], undefined), + <<48, 162>> = test_op_bs_create_bin_asm:bs_create_bin_utf16_fail(12450, [], undefined), + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_utf16_no_fail(-1, [], undefined), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_utf16_fail(-1, [], undefined), + ok + catch + error:badarg -> unexpected + end, + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_utf16_no_fail(not_int, [], undefined), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_utf16_fail(not_int, [], undefined), + ok + catch + error:badarg -> unexpected + end, + <<0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf16_no_fail(0, [], all), + <<0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf16_no_fail(0, [], 8), + <<0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf16_no_fail(0, [], 16), + <<0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf16_no_fail(0, [], foo), + ok. + +test_bs_create_bin_utf32() -> + <<0, 0, 0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf32_no_fail(0, [], undefined), + <<0, 0, 0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf32_fail(0, [], undefined), + <<0, 0, 0, 1>> = test_op_bs_create_bin_asm:bs_create_bin_utf32_no_fail(1, [], undefined), + <<0, 0, 0, 1>> = test_op_bs_create_bin_asm:bs_create_bin_utf32_fail(1, [], undefined), + <<0, 0, 48, 162>> = test_op_bs_create_bin_asm:bs_create_bin_utf32_no_fail(12450, [], undefined), + <<0, 0, 48, 162>> = test_op_bs_create_bin_asm:bs_create_bin_utf32_fail(12450, [], undefined), + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_utf32_no_fail(-1, [], undefined), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_utf32_fail(-1, [], undefined), + ok + catch + error:badarg -> unexpected + end, + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_utf32_no_fail(not_int, [], undefined), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_utf32_fail(not_int, [], undefined), + ok + catch + error:badarg -> unexpected + end, + <<0, 0, 0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf32_no_fail(0, [], all), + <<0, 0, 0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf32_no_fail(0, [], 8), + <<0, 0, 0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf32_no_fail(0, [], 16), + <<0, 0, 0, 0>> = test_op_bs_create_bin_asm:bs_create_bin_utf32_no_fail(0, [], foo), + ok. + +test_bs_create_bin_integer() -> + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_integer_no_fail(0, [], undefined), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_integer_fail(0, [], undefined), + ok + catch + error:badarg -> unexpected + end, + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_integer_no_fail(0, [], all), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_integer_fail(0, [], all), + ok + catch + error:badarg -> unexpected + end, + <<42>> = test_op_bs_create_bin_asm:bs_create_bin_integer_no_fail(42, [], 1), + <<42>> = test_op_bs_create_bin_asm:bs_create_bin_integer_fail(42, [], 1), + <<0, 42>> = test_op_bs_create_bin_asm:bs_create_bin_integer_no_fail(42, [], 2), + <<0, 42>> = test_op_bs_create_bin_asm:bs_create_bin_integer_fail(42, [], 2), + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_integer_no_fail(0, [], foo), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_integer_fail(0, [], foo), + ok + catch + error:badarg -> unexpected + end, + ok. + +test_bs_create_bin_binary() -> + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_binary_no_fail(<<0>>, [], undefined), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_binary_fail(<<0>>, [], undefined), + ok + catch + error:badarg -> unexpected + end, + % AtomVM allows all for binaries while OTP28 seems to disallow it. + % It may only accept them for bs match state (?) + % ok = try test_op_bs_create_bin_asm:bs_create_bin_binary_no_fail(<<0>>, [], all), unexpected catch error:badarg -> ok end, + % ok = try fail = test_op_bs_create_bin_asm:bs_create_bin_binary_fail(<<0>>, [], all), ok catch error:badarg -> unexpected end, + <<42>> = test_op_bs_create_bin_asm:bs_create_bin_binary_no_fail(<<42>>, [], 1), + <<42>> = test_op_bs_create_bin_asm:bs_create_bin_binary_fail(<<42>>, [], 1), + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_binary_no_fail(<<42>>, [], 2), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_binary_fail(<<42>>, [], 2), + ok + catch + error:badarg -> unexpected + end, + ok = + try + test_op_bs_create_bin_asm:bs_create_bin_binary_no_fail(<<0>>, [], foo), + unexpected + catch + error:badarg -> ok + end, + ok = + try + fail = test_op_bs_create_bin_asm:bs_create_bin_binary_fail(<<0>>, [], foo), + ok + catch + error:badarg -> unexpected + end, + ok. diff --git a/tests/erlang_tests/test_op_bs_create_bin_asm.S b/tests/erlang_tests/test_op_bs_create_bin_asm.S new file mode 100644 index 000000000..ab203d953 --- /dev/null +++ b/tests/erlang_tests/test_op_bs_create_bin_asm.S @@ -0,0 +1,124 @@ +% +% This file is part of AtomVM. +% +% Copyright 2025 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 +% + +%% version = 0 +{module, test_op_bs_create_bin_asm}. + +{exports, [ + {bs_create_bin_utf8_no_fail, 3}, + {bs_create_bin_utf8_fail, 3}, + {bs_create_bin_utf16_no_fail, 3}, + {bs_create_bin_utf16_fail, 3}, + {bs_create_bin_utf32_no_fail, 3}, + {bs_create_bin_utf32_fail, 3}, + {bs_create_bin_integer_no_fail, 3}, + {bs_create_bin_integer_fail, 3}, + {bs_create_bin_binary_no_fail, 3}, + {bs_create_bin_binary_fail, 3} +]}. + +{attributes, []}. + +{labels, 16}. + +{function, bs_create_bin_utf8_no_fail, 3, 2}. +{label, 1}. +{func_info, {atom, test_op_bs_create_bin_asm}, {atom, bs_create_bin_utf8_no_fail}, 3}. +{label, 2}. +{bs_create_bin, {f, 0}, 0, 1, 8, {x, 0}, {list, [{atom, utf8}, 1, 0, {x, 1}, {x, 0}, {x, 2}]}}. +return. + +{function, bs_create_bin_utf8_fail, 3, 5}. +{label, 4}. +{func_info, {atom, test_op_bs_create_bin_asm}, {atom, bs_create_bin_utf8_fail}, 3}. +{label, 5}. +{bs_create_bin, {f, 6}, 0, 1, 8, {x, 0}, {list, [{atom, utf8}, 1, 0, {x, 1}, {x, 0}, {x, 2}]}}. +return. +{label, 6}. +{move, {atom, fail}, {x, 0}}. +return. + +{function, bs_create_bin_utf16_no_fail, 3, 8}. +{label, 7}. +{func_info, {atom, test_op_bs_create_bin_asm}, {atom, bs_create_bin_utf16_no_fail}, 3}. +{label, 8}. +{bs_create_bin, {f, 0}, 0, 1, 8, {x, 0}, {list, [{atom, utf16}, 1, 0, {x, 1}, {x, 0}, {x, 2}]}}. +return. + +{function, bs_create_bin_utf16_fail, 3, 10}. +{label, 9}. +{func_info, {atom, test_op_bs_create_bin_asm}, {atom, bs_create_bin_utf16_fail}, 3}. +{label, 10}. +{bs_create_bin, {f, 11}, 0, 1, 8, {x, 0}, {list, [{atom, utf16}, 1, 0, {x, 1}, {x, 0}, {x, 2}]}}. +return. +{label, 11}. +{move, {atom, fail}, {x, 0}}. +return. + +{function, bs_create_bin_utf32_no_fail, 3, 13}. +{label, 12}. +{func_info, {atom, test_op_bs_create_bin_asm}, {atom, bs_create_bin_utf32_no_fail}, 3}. +{label, 13}. +{bs_create_bin, {f, 0}, 0, 1, 8, {x, 0}, {list, [{atom, utf32}, 1, 0, {x, 1}, {x, 0}, {x, 2}]}}. +return. + +{function, bs_create_bin_utf32_fail, 3, 15}. +{label, 14}. +{func_info, {atom, test_op_bs_create_bin_asm}, {atom, bs_create_bin_utf32_fail}, 3}. +{label, 15}. +{bs_create_bin, {f, 16}, 0, 1, 8, {x, 0}, {list, [{atom, utf32}, 1, 0, {x, 1}, {x, 0}, {x, 2}]}}. +return. +{label, 16}. +{move, {atom, fail}, {x, 0}}. +return. + +{function, bs_create_bin_integer_no_fail, 3, 18}. +{label, 17}. +{func_info, {atom, test_op_bs_create_bin_asm}, {atom, bs_create_bin_integer_no_fail}, 3}. +{label, 18}. +{bs_create_bin, {f, 0}, 0, 1, 8, {x, 0}, {list, [{atom, integer}, 1, 8, {x, 1}, {x, 0}, {x, 2}]}}. +return. + +{function, bs_create_bin_integer_fail, 3, 20}. +{label, 19}. +{func_info, {atom, test_op_bs_create_bin_asm}, {atom, bs_create_bin_integer_fail}, 3}. +{label, 20}. +{bs_create_bin, {f, 21}, 0, 1, 8, {x, 0}, {list, [{atom, integer}, 1, 8, {x, 1}, {x, 0}, {x, 2}]}}. +return. +{label, 21}. +{move, {atom, fail}, {x, 0}}. +return. + +{function, bs_create_bin_binary_no_fail, 3, 23}. +{label, 22}. +{func_info, {atom, test_op_bs_create_bin_asm}, {atom, bs_create_bin_binary_no_fail}, 3}. +{label, 23}. +{bs_create_bin, {f, 0}, 0, 1, 8, {x, 0}, {list, [{atom, binary}, 1, 8, {x, 1}, {x, 0}, {x, 2}]}}. +return. + +{function, bs_create_bin_binary_fail, 3, 25}. +{label, 24}. +{func_info, {atom, test_op_bs_create_bin_asm}, {atom, bs_create_bin_binary_fail}, 3}. +{label, 25}. +{bs_create_bin, {f, 26}, 0, 1, 8, {x, 0}, {list, [{atom, binary}, 1, 8, {x, 1}, {x, 0}, {x, 2}]}}. +return. +{label, 26}. +{move, {atom, fail}, {x, 0}}. +return. diff --git a/tests/test.c b/tests/test.c index 1cd9f4114..2dd335602 100644 --- a/tests/test.c +++ b/tests/test.c @@ -537,6 +537,8 @@ struct Test tests[] = { TEST_CASE(test_module_info), + TEST_CASE(test_op_bs_create_bin), + // noisy tests, keep them at the end TEST_CASE_EXPECTED(spawn_opt_monitor_normal, 1), TEST_CASE_EXPECTED(spawn_opt_link_normal, 1),