Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions src/libAtomVM/term.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions tests/erlang_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
114 changes: 114 additions & 0 deletions tests/erlang_tests/test_function_ordering.erl
Original file line number Diff line number Diff line change
@@ -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,
<<Prefix:Size/binary, V:8>> = Binary,
<<Prefix/binary, (V + 1):8>>.

id(X) -> X.
1 change: 1 addition & 0 deletions tests/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Loading