diff --git a/src/libAtomVM/term.c b/src/libAtomVM/term.c index 4b55b1df54..eb627dd018 100644 --- a/src/libAtomVM/term.c +++ b/src/libAtomVM/term.c @@ -819,6 +819,92 @@ TermCompareResult term_compare(term t, term other, TermCompareOpts opts, GlobalC } } } + case TERM_TYPE_INDEX_FUNCTION: { + if (term_is_external_fun(t) && term_is_external_fun(other)) { + const term *boxed_value = term_to_const_term_ptr(t); + term module_atom = boxed_value[1]; + term function_atom = boxed_value[2]; + term arity = boxed_value[3]; + + const term *other_boxed_value = term_to_const_term_ptr(other); + term other_module_atom = other_boxed_value[1]; + term other_function_atom = other_boxed_value[2]; + term other_arity = other_boxed_value[3]; + + if (temp_stack_push(&temp_stack, arity) != TempStackOk + || temp_stack_push(&temp_stack, other_arity) != TempStackOk + || temp_stack_push(&temp_stack, function_atom) != TempStackOk + || temp_stack_push(&temp_stack, other_function_atom) != TempStackOk) { + return TermCompareMemoryAllocFail; + } + + t = module_atom; + other = other_module_atom; + } else if (!term_is_external_fun(t) && !term_is_external_fun(other)) { + const term *boxed_value = term_to_const_term_ptr(t); + Module *fun_module = (Module *) boxed_value[1]; + term module_name_atom = module_get_name(fun_module); + atom_index_t module_atom_index = term_to_atom_index(module_name_atom); + + const term *other_boxed_value = term_to_const_term_ptr(other); + Module *other_fun_module = (Module *) other_boxed_value[1]; + term other_module_name_atom = module_get_name(other_fun_module); + atom_index_t other_module_atom_index = term_to_atom_index(other_module_name_atom); + + int module_cmp_result = atom_table_cmp_using_atom_index( + global->atom_table, module_atom_index, other_module_atom_index); + + if (module_cmp_result != 0) { + result = (module_cmp_result > 0) ? TermGreaterThan : TermLessThan; + goto unequal; + } + + uint32_t fun_index = term_to_int32(boxed_value[2]); + uint32_t other_fun_index = term_to_int32(other_boxed_value[2]); + + if (fun_index != other_fun_index) { + result = (fun_index > other_fun_index) ? TermGreaterThan : TermLessThan; + goto unequal; + } + + uint32_t arity, old_index, old_uniq; + module_get_fun_arity_old_index_uniq(fun_module, fun_index, &arity, &old_index, &old_uniq); + uint32_t other_arity, other_old_index, other_old_uniq; + module_get_fun_arity_old_index_uniq(other_fun_module, other_fun_index, &other_arity, &other_old_index, &other_old_uniq); + + if (old_uniq != other_old_uniq) { + result = (old_uniq > other_old_uniq) ? TermGreaterThan : TermLessThan; + goto unequal; + } + + uint32_t num_freeze = module_get_fun_freeze(fun_module, fun_index); + uint32_t other_num_freeze = module_get_fun_freeze(other_fun_module, other_fun_index); + + if (num_freeze != other_num_freeze) { + result = (num_freeze > other_num_freeze) ? TermGreaterThan : TermLessThan; + goto unequal; + } else if (num_freeze == 0) { + CMP_POP_AND_CONTINUE(); + } else { + uint32_t freeze_base = 3; + for (uint32_t i = num_freeze - 1; i >= 1; i--) { + if (temp_stack_push(&temp_stack, boxed_value[i + freeze_base]) != TempStackOk + || temp_stack_push(&temp_stack, other_boxed_value[i + freeze_base]) != TempStackOk) { + return TermCompareMemoryAllocFail; + } + } + + t = boxed_value[freeze_base]; + other = other_boxed_value[freeze_base]; + } + + } else { + result = term_is_external_fun(t) ? TermGreaterThan : TermLessThan; + goto unequal; + } + + break; + } case TERM_TYPE_INDEX_NON_EMPTY_LIST: { term t_tail = term_get_list_tail(t); term other_tail = term_get_list_tail(other); diff --git a/tests/erlang_tests/CMakeLists.txt b/tests/erlang_tests/CMakeLists.txt index ed8f4ac8fc..888eef8199 100644 --- a/tests/erlang_tests/CMakeLists.txt +++ b/tests/erlang_tests/CMakeLists.txt @@ -421,6 +421,7 @@ compile_erlang(test_types_ordering) compile_erlang(test_bigintegers_ordering) compile_erlang(test_refs_ordering) compile_erlang(test_atom_ordering) +compile_erlang(test_function_ordering) compile_erlang(test_pids_ordering) compile_erlang(test_list_match) compile_erlang(test_match) @@ -949,6 +950,7 @@ set(erlang_test_beams test_bigintegers_ordering.beam test_refs_ordering.beam test_atom_ordering.beam + test_function_ordering.beam test_pids_ordering.beam test_list_match.beam test_match.beam diff --git a/tests/erlang_tests/test_function_ordering.erl b/tests/erlang_tests/test_function_ordering.erl new file mode 100644 index 0000000000..04829459f5 --- /dev/null +++ b/tests/erlang_tests/test_function_ordering.erl @@ -0,0 +1,114 @@ +% +% This file is part of AtomVM. +% +% Copyright 2026 AtomVM Contributors +% +% 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_function_ordering). + +-export([start/0, id/1]). + +start() -> + Self = self(), + Self2 = ?MODULE:id(self()), + Five = ?MODULE:id(5), + Six = ?MODULE:id(6), + F1 = fun(X, Y) -> X + Y end, + F2 = fun(X, Y) -> X + Y end, + F3 = fun(X) -> X + Five + Six end, + % Increasing the last byte of the binary representation + % effectively increases the last value of the closure, + % which is the value of variable Six. Thus, F4 only differs + % form F3 by the last element of the closure. + F4 = erlang:binary_to_term(increase_last_byte(erlang:term_to_binary(F3))), + F5 = fun() -> Self ! hello end, + F6 = fun() -> Self ! Self2 end, + F7 = fun() -> Self ! Five end, + F8 = fun(X) -> Self ! {X, Self} end, + F9 = fun(X, Y) -> Self ! {X, Y, Self} end, + + true = F3 == erlang:binary_to_term(erlang:term_to_binary(F3)), + + Funs = [ + F4, + F2, + fun erlang:exit/1, + F9, + F3, + F6, + fun application:ensure_started/1, + F5, + F8, + F1, + fun erlang:apply/3, + F7 + ], + false = has_duplicates(Funs), + true = ?MODULE:id(Funs) == ?MODULE:id(Funs), + true = ?MODULE:id(Funs) =:= ?MODULE:id(Funs), + Sorted = sort(Funs), + Sorted = [ + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + fun application:ensure_started/1, + fun erlang:apply/3, + fun erlang:exit/1 + ], + true = Sorted < [fun erlang:whereis/1], + true = Sorted > {fun erlang:whereis/1}, + 0. + +sort(L) -> + sort(L, []). + +sort([], Sorted) -> + Sorted; +sort([H | Unsorted], Sorted) -> + NextSorted = insert(Sorted, H), + sort(Unsorted, NextSorted). + +insert(L, I) -> + insert(L, [], I). + +insert([], HL, I) -> + HL ++ [I]; +insert([H | T], HL, I) when I < H -> + HL ++ [I, H | T]; +insert([H | T], HL, I) -> + insert(T, HL ++ [H], I). + +has_duplicates([]) -> + false; +has_duplicates([H | T]) -> + case lists:member(H, T) of + true -> true; + false -> has_duplicates(T) + end. + +increase_last_byte(Binary) -> + Size = byte_size(Binary) - 1, + <> = Binary, + <>. + +id(X) -> X. diff --git a/tests/test.c b/tests/test.c index f1024f27ed..ea62ffd8e9 100644 --- a/tests/test.c +++ b/tests/test.c @@ -397,6 +397,7 @@ struct Test tests[] = { TEST_CASE_EXPECTED(test_bigintegers_ordering, 7), TEST_CASE_EXPECTED(test_refs_ordering, 7), TEST_CASE_EXPECTED(test_atom_ordering, 7), + TEST_CASE(test_function_ordering), TEST_CASE_EXPECTED(test_pids_ordering, 7), TEST_CASE_EXPECTED(test_list_match, 31), TEST_CASE(test_match),